Private
Public Access
1
0
Files
u-desk/web/src/views/db-cli/utils/resultExporter.js
绝尘 e5dbe89a6f 新增:Markdown编辑器/数据库优化/安全修复
- Markdown 编辑器:实时预览、PDF 导出、独立查看器
- 数据库优化:动态连接池、查询缓存、Redis Pipeline
- 窗口置顶功能
- 文件系统增强:右键菜单、编辑器集成、收藏夹重构
- 安全修复:XSS 防护、路径穿越、HTML 注入
- 代码质量:正则预编译、缓存锁优化、死代码清理
2026-03-31 11:49:25 +08:00

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
}
}