- Markdown 编辑器:实时预览、PDF 导出、独立查看器 - 数据库优化:动态连接池、查询缓存、Redis Pipeline - 窗口置顶功能 - 文件系统增强:右键菜单、编辑器集成、收藏夹重构 - 安全修复:XSS 防护、路径穿越、HTML 注入 - 代码质量:正则预编译、缓存锁优化、死代码清理
220 lines
5.2 KiB
JavaScript
220 lines
5.2 KiB
JavaScript
/**
|
|
* 查询结果导出工具
|
|
* 支持 CSV、JSON、Excel 格式
|
|
*/
|
|
|
|
import { escapeHtml } from '@/utils/fileUtils'
|
|
|
|
/**
|
|
* 导出为 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)
|
|
}
|
|
|
|
/**
|
|
* 复制到剪贴板
|
|
*/
|
|
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
|
|
}
|
|
}
|