新增:数据库 UI UX 大幅改进
功能增强: - 查询历史记录与快速重用(最多50条) - 查询模板管理(9个默认模板,支持自定义) - SQL 格式化功能(关键字大写、缩进美化) - 查询结果导出(CSV/JSON/Excel/Markdown) - 执行时间显示(带颜色指示:绿/橙/红) - 增强工具栏(整合所有功能) 新增组件: - QueryHistoryPanel.vue - 查询历史面板 - QueryTemplatesPanel.vue - 查询模板面板 - SQLEditorToolbar.vue - 增强工具栏 - useQueryHistory.js - 历史记录管理 - useQueryTemplates.js - 模板管理 - sqlFormatter.js - SQL 格式化工具 - resultExporter.js - 结果导出工具 修改组件: - SqlEditor.vue - 集成新功能与工具栏
This commit is contained in:
231
web/src/views/db-cli/utils/resultExporter.js
Normal file
231
web/src/views/db-cli/utils/resultExporter.js
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* 查询结果导出工具
|
||||
* 支持 CSV、JSON、Excel 格式
|
||||
*/
|
||||
|
||||
/**
|
||||
* 导出为 CSV
|
||||
*/
|
||||
export function exportToCSV(data, columns = [], filename = 'query-result.csv') {
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('No data to export')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 自动检测列名
|
||||
let headers = columns
|
||||
if (headers.length === 0) {
|
||||
headers = Object.keys(data[0])
|
||||
}
|
||||
|
||||
// 构建 CSV 内容
|
||||
const rows = []
|
||||
|
||||
// 表头
|
||||
rows.push(headers.join(','))
|
||||
|
||||
// 数据行
|
||||
data.forEach(row => {
|
||||
const values = headers.map(header => {
|
||||
const value = row[header]
|
||||
// 处理 null/undefined
|
||||
if (value === null || value === undefined) return ''
|
||||
// 处理包含逗号或引号的值
|
||||
const strValue = String(value)
|
||||
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
|
||||
return `"${strValue.replace(/"/g, '""')}"`
|
||||
}
|
||||
return strValue
|
||||
})
|
||||
rows.push(values.join(','))
|
||||
})
|
||||
|
||||
// 添加 BOM 使 Excel 正确识别 UTF-8
|
||||
const BOM = '\uFEFF'
|
||||
const csvContent = BOM + rows.join('\n')
|
||||
|
||||
// 创建下载
|
||||
downloadFile(csvContent, filename, 'text/csv;charset=utf-8')
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Failed to export CSV:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为 JSON
|
||||
*/
|
||||
export function exportToJSON(data, filename = 'query-result.json', pretty = true) {
|
||||
if (!data || !Array.isArray(data)) {
|
||||
console.warn('No data to export')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonContent = pretty
|
||||
? JSON.stringify(data, null, 2)
|
||||
: JSON.stringify(data)
|
||||
|
||||
downloadFile(jsonContent, filename, 'application/json;charset=utf-8')
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Failed to export JSON:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为 Markdown 表格
|
||||
*/
|
||||
export function exportToMarkdown(data, columns = [], filename = 'query-result.md') {
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('No data to export')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 自动检测列名
|
||||
let headers = columns
|
||||
if (headers.length === 0) {
|
||||
headers = Object.keys(data[0])
|
||||
}
|
||||
|
||||
// 构建 Markdown 表格
|
||||
const rows = []
|
||||
|
||||
// 表头
|
||||
rows.push('| ' + headers.join(' | ') + ' |')
|
||||
rows.push('| ' + headers.map(() => '---').join(' | ') + ' |')
|
||||
|
||||
// 数据行
|
||||
data.forEach(row => {
|
||||
const values = headers.map(header => {
|
||||
const value = row[header]
|
||||
if (value === null || value === undefined) return ''
|
||||
// 转义管道符
|
||||
return String(value).replace(/\|/g, '\\|')
|
||||
})
|
||||
rows.push('| ' + values.join(' | ') + ' |')
|
||||
})
|
||||
|
||||
const mdContent = rows.join('\n')
|
||||
downloadFile(mdContent, filename, 'text/markdown;charset=utf-8')
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Failed to export Markdown:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为 Excel (HTML 表格格式)
|
||||
*/
|
||||
export function exportToExcel(data, columns = [], filename = 'query-result.xls') {
|
||||
if (!data || !Array.isArray(data) || data.length === 0) {
|
||||
console.warn('No data to export')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 自动检测列名
|
||||
let headers = columns
|
||||
if (headers.length === 0) {
|
||||
headers = Object.keys(data[0])
|
||||
}
|
||||
|
||||
// 构建 HTML 表格
|
||||
let html = '<table>\n'
|
||||
|
||||
// 表头
|
||||
html += ' <thead>\n <tr>\n'
|
||||
headers.forEach(header => {
|
||||
html += ` <th><b>${escapeHtml(header)}</b></th>\n`
|
||||
})
|
||||
html += ' </tr>\n </thead>\n'
|
||||
|
||||
// 表体
|
||||
html += ' <tbody>\n'
|
||||
data.forEach(row => {
|
||||
html += ' <tr>\n'
|
||||
headers.forEach(header => {
|
||||
const value = row[header]
|
||||
const displayValue = value === null || value === undefined ? '' : String(value)
|
||||
html += ` <td>${escapeHtml(displayValue)}</td>\n`
|
||||
})
|
||||
html += ' </tr>\n'
|
||||
})
|
||||
html += ' </tbody>\n'
|
||||
|
||||
html += '</table>'
|
||||
|
||||
downloadFile(html, filename, 'application/vnd.ms-excel;charset=utf-8')
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Failed to export Excel:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
*/
|
||||
function downloadFile(content, filename, mimeType) {
|
||||
const blob = new Blob([content], { type: mimeType })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 转义
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}
|
||||
return text.replace(/[&<>"']/g, m => map[m])
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制到剪贴板
|
||||
*/
|
||||
export async function copyToClipboard(data, format = 'json') {
|
||||
if (!data) return false
|
||||
|
||||
try {
|
||||
let content = ''
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
content = JSON.stringify(data, null, 2)
|
||||
break
|
||||
case 'csv':
|
||||
if (data.length === 0) return false
|
||||
const headers = Object.keys(data[0])
|
||||
content = headers.join(',') + '\n'
|
||||
data.forEach(row => {
|
||||
content += headers.map(h => row[h] ?? '').join(',') + '\n'
|
||||
})
|
||||
break
|
||||
default:
|
||||
content = String(data)
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(content)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('Failed to copy to clipboard:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user