Private
Public Access
1
0

新增:数据库 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:
2026-02-13 01:20:52 +08:00
parent 4a1f0213df
commit 22f5862f15
8 changed files with 1615 additions and 39 deletions

View 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}
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
}
}