Implement DataTable component for improved table rendering and add export functionality in QueryEditor
This commit is contained in:
parent
96be70c976
commit
ebbbe46d22
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
@ -383,6 +383,10 @@ function TableViewer({ tab, onLoadPage }: {
|
|||||||
<span className="text-white/40 text-sm">({tab.total} 行)</span>
|
<span className="text-white/40 text-sm">({tab.total} 行)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span className="text-xs text-white/30 flex items-center gap-1">
|
||||||
|
<Pin size={12} /> 点击列头图钉可固定列
|
||||||
|
</span>
|
||||||
|
|
||||||
{/* 分页控制 */}
|
{/* 分页控制 */}
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
<button
|
<button
|
||||||
@ -405,69 +409,14 @@ function TableViewer({ tab, onLoadPage }: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 数据表格 - 使用绝对定位确保滚动 */}
|
{/* 数据表格 - 使用 DataTable 组件支持列固定 */}
|
||||||
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
||||||
<div style={{
|
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||||
position: 'absolute',
|
<DataTable
|
||||||
top: 0,
|
columns={tab.columns}
|
||||||
left: 0,
|
data={tab.data}
|
||||||
right: 0,
|
showColumnInfo={true}
|
||||||
bottom: 0,
|
/>
|
||||||
overflow: 'auto'
|
|
||||||
}}>
|
|
||||||
<table className="text-sm border-collapse" style={{ minWidth: 'max-content' }}>
|
|
||||||
<thead className="sticky top-0 z-10">
|
|
||||||
<tr>
|
|
||||||
{tab.columns.map((col, i) => (
|
|
||||||
<th
|
|
||||||
key={i}
|
|
||||||
className="px-4 py-2 text-left font-medium border-b border-metro-border whitespace-nowrap"
|
|
||||||
style={{ background: '#2d2d2d' }}
|
|
||||||
title={col.comment ? `${col.name}\n类型: ${col.type}\n备注: ${col.comment}` : `${col.name}\n类型: ${col.type}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
{col.key === 'PRI' && <Key size={12} className="text-accent-orange" />}
|
|
||||||
<span className="text-accent-blue">{col.name}</span>
|
|
||||||
<span className="text-white/30 font-normal text-xs">({col.type})</span>
|
|
||||||
{col.comment && (
|
|
||||||
<span className="text-accent-green text-xs" title={col.comment}>
|
|
||||||
<Info size={12} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{col.comment && (
|
|
||||||
<div className="text-xs text-white/40 font-normal mt-0.5 max-w-[200px] truncate">
|
|
||||||
{col.comment}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{tab.data.map((row, i) => (
|
|
||||||
<tr key={i} className="hover:bg-metro-surface/50">
|
|
||||||
{tab.columns.map((col, j) => (
|
|
||||||
<td key={j} className="px-4 py-1.5 border-b border-metro-border/50 font-mono text-white/80 whitespace-nowrap">
|
|
||||||
{row[col.name] === null ? (
|
|
||||||
<span className="text-white/30 italic">NULL</span>
|
|
||||||
) : typeof row[col.name] === 'object' ? (
|
|
||||||
<span className="text-accent-purple">{JSON.stringify(row[col.name])}</span>
|
|
||||||
) : (
|
|
||||||
String(row[col.name])
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{tab.data.length === 0 && (
|
|
||||||
<div className="h-32 flex items-center justify-center text-white/30">
|
|
||||||
暂无数据
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -569,4 +518,147 @@ function QueryEditor({ tab, databases, tables, columns, onRun, onUpdateSql, onUp
|
|||||||
sqlContent += `INSERT INTO \`${tableName}\` (\`${columns.join('`, `')}\`) VALUES (${values});\n`
|
sqlContent += `INSERT INTO \`${tableName}\` (\`${columns.join('`, `')}\`) VALUES (${values});\n`
|
||||||
})
|
})
|
||||||
|
|
||||||
const blob = n
|
const blob = new Blob([sqlContent], { type: 'text/plain;charset=utf-8' })
|
||||||
|
saveAs(blob, `query_results_${Date.now()}.sql`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出下拉菜单状态
|
||||||
|
const [showExportMenu, setShowExportMenu] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
{/* SQL 编辑区 */}
|
||||||
|
<div style={{ height: '200px', flexShrink: 0, display: 'flex', flexDirection: 'column', borderBottom: '1px solid #5d5d5d' }}>
|
||||||
|
<div className="h-10 bg-metro-bg flex items-center px-2 gap-2" style={{ flexShrink: 0 }}>
|
||||||
|
<button
|
||||||
|
onClick={handleRun}
|
||||||
|
className="h-7 px-4 bg-accent-green hover:bg-accent-green/90 flex items-center gap-1.5 text-sm transition-colors"
|
||||||
|
title="执行 SQL (Ctrl+Enter)"
|
||||||
|
>
|
||||||
|
<Play size={14} fill="currentColor" />
|
||||||
|
执行
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="w-px h-5 bg-white/20 mx-1" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleOpenFile}
|
||||||
|
className="h-7 px-3 bg-metro-surface hover:bg-metro-surface/80 flex items-center gap-1.5 text-sm transition-colors"
|
||||||
|
title="打开 SQL 文件 (Ctrl+O)"
|
||||||
|
>
|
||||||
|
<FolderOpen size={14} />
|
||||||
|
打开
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleSaveFile}
|
||||||
|
className="h-7 px-3 bg-metro-surface hover:bg-metro-surface/80 flex items-center gap-1.5 text-sm transition-colors"
|
||||||
|
title="保存 SQL 文件 (Ctrl+S)"
|
||||||
|
>
|
||||||
|
<Save size={14} />
|
||||||
|
保存
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleFormat}
|
||||||
|
className="h-7 px-3 bg-metro-surface hover:bg-metro-surface/80 flex items-center gap-1.5 text-sm transition-colors"
|
||||||
|
title="格式化 SQL (Ctrl+Shift+F)"
|
||||||
|
>
|
||||||
|
<AlignLeft size={14} />
|
||||||
|
格式化
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="w-px h-5 bg-white/20 mx-1" />
|
||||||
|
|
||||||
|
{/* 导出按钮 */}
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowExportMenu(!showExportMenu)}
|
||||||
|
className="h-7 px-3 bg-metro-surface hover:bg-metro-surface/80 flex items-center gap-1.5 text-sm transition-colors"
|
||||||
|
title="导出结果"
|
||||||
|
disabled={!tab.results || tab.results.rows.length === 0}
|
||||||
|
>
|
||||||
|
<Download size={14} />
|
||||||
|
导出
|
||||||
|
</button>
|
||||||
|
{showExportMenu && (
|
||||||
|
<div className="absolute top-full left-0 mt-1 bg-metro-surface border border-metro-border rounded shadow-lg z-50 min-w-[140px]">
|
||||||
|
<button
|
||||||
|
onClick={() => { handleExportExcel(); setShowExportMenu(false) }}
|
||||||
|
className="w-full px-3 py-2 text-left text-sm hover:bg-accent-blue/20 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<FileSpreadsheet size={14} className="text-accent-green" />
|
||||||
|
导出 Excel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => { handleExportSql(); setShowExportMenu(false) }}
|
||||||
|
className="w-full px-3 py-2 text-left text-sm hover:bg-accent-blue/20 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<FileCode size={14} className="text-accent-orange" />
|
||||||
|
导出 SQL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="text-xs text-white/40 ml-auto">
|
||||||
|
{filePath && <span className="mr-3 text-accent-blue">{filePath.split(/[/\\]/).pop()}</span>}
|
||||||
|
Ctrl+Enter 执行 | Ctrl+S 保存 | Ctrl+Shift+F 格式化
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, minHeight: 0 }}>
|
||||||
|
<SqlEditor
|
||||||
|
value={sql}
|
||||||
|
onChange={setSql}
|
||||||
|
onRun={handleRun}
|
||||||
|
onSave={handleSaveFile}
|
||||||
|
onOpen={handleOpenFile}
|
||||||
|
onFormat={handleFormat}
|
||||||
|
databases={databases}
|
||||||
|
tables={tables}
|
||||||
|
columns={columns}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 结果区 - 使用 DataTable 组件支持列固定 */}
|
||||||
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||||
|
<div className="h-9 bg-metro-bg flex items-center px-3 border-b border-metro-border" style={{ flexShrink: 0 }}>
|
||||||
|
<span className="text-sm text-white/60">
|
||||||
|
结果
|
||||||
|
{tab.results && <span className="ml-2 text-white/40">({tab.results.rows.length} 行)</span>}
|
||||||
|
</span>
|
||||||
|
{tab.results && tab.results.rows.length > 0 && (
|
||||||
|
<span className="text-xs text-white/30 ml-4 flex items-center gap-1">
|
||||||
|
<Pin size={12} /> 点击列头图钉可固定列
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
||||||
|
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||||
|
{tab.results ? (
|
||||||
|
<DataTable
|
||||||
|
columns={tab.results.columns.map(col => {
|
||||||
|
const colInfo = findColumnInfo(col)
|
||||||
|
return {
|
||||||
|
name: col,
|
||||||
|
type: colInfo?.type,
|
||||||
|
key: colInfo?.key,
|
||||||
|
comment: colInfo?.comment,
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
data={tab.results.rows}
|
||||||
|
showColumnInfo={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-full flex items-center justify-center text-white/30">
|
||||||
|
执行查询以查看结果
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user