新增 xlsx 库支持,优化 Excel 导出功能,更新相关文档
This commit is contained in:
@@ -71,66 +71,77 @@ export async function copyToClipboard(text) {
|
||||
}
|
||||
}
|
||||
|
||||
// 导出CSV数据
|
||||
export function exportToCSV(data, headers, filename) {
|
||||
const csvContent = [
|
||||
headers.map(h => h.label).join(','),
|
||||
...data.map(row =>
|
||||
headers.map(header => {
|
||||
const value = row[header.key] || ''
|
||||
// 处理包含逗号的值
|
||||
return value.toString().includes(',') ? `"${value}"` : value
|
||||
}).join(',')
|
||||
)
|
||||
].join('\n')
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
// 计算列宽(中文按双字节处理)
|
||||
function calcWch(val) {
|
||||
const s = (val ?? '').toString()
|
||||
let w = 0
|
||||
for (const ch of s) w += ch.charCodeAt(0) > 255 ? 2 : 1
|
||||
return Math.min(Math.max(w, 8), 60) // 8~60 字符宽
|
||||
}
|
||||
|
||||
// 导出Excel数据
|
||||
export function exportToExcel(data, headers, filename) {
|
||||
// 创建HTML表格格式
|
||||
const tableHTML = `
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
th { background-color: #f2f2f2; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
${headers.map(header => `<th>${header.label}</th>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${data.map(row =>
|
||||
`<tr>
|
||||
${headers.map(header => `<td>${row[header.key] || ''}</td>`).join('')}
|
||||
</tr>`
|
||||
).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// 创建Blob并下载
|
||||
const blob = new Blob([tableHTML], {
|
||||
type: 'application/vnd.ms-excel;charset=utf-8;'
|
||||
// 简单日期格式化(如果字段是 Date 或 ISO 字符串)
|
||||
function fmtDate(v) {
|
||||
const d = v instanceof Date ? v : (typeof v === 'string' && !isNaN(Date.parse(v)) ? new Date(v) : null)
|
||||
if (!d) return v
|
||||
const pad = n => (n < 10 ? '0' + n : '' + n)
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
}
|
||||
|
||||
// 真实 .xlsx 导出(中文不会乱码)
|
||||
export function exportToExcel(data, headers, filename = '导出.xlsx', sheetName = 'Sheet1') {
|
||||
console.log('xlsx导出数据:', data)
|
||||
// 动态导入 xlsx 库
|
||||
import('xlsx').then(({ utils: XLSXUtils, writeFile: XLSXWriteFile }) => {
|
||||
// 1) 规范化数据:按 headers 顺序映射到"显示列名 -> 值"
|
||||
const headerLabels = headers.map(h => h.label)
|
||||
const rows = (data || []).map(row => {
|
||||
const obj = {}
|
||||
headers.forEach(h => {
|
||||
let v = row?.[h.key]
|
||||
// 识别并格式化日期
|
||||
v = fmtDate(v)
|
||||
|
||||
// 复杂对象转字符串
|
||||
if (v && typeof v === 'object' && !(v instanceof Date)) {
|
||||
try { v = JSON.stringify(v) } catch { v = String(v) }
|
||||
}
|
||||
|
||||
// 公式注入防护(以 = + - @ 开头的文本加前导单引号)
|
||||
if (typeof v === 'string' && /^[=+\-@]/.test(v)) v = "'" + v
|
||||
|
||||
// null/undefined 转为空串,保留 0/false
|
||||
if (v === null || v === undefined) v = ''
|
||||
obj[h.label] = v
|
||||
})
|
||||
return obj
|
||||
})
|
||||
|
||||
// 2) 生成工作表
|
||||
const ws = XLSXUtils.json_to_sheet(rows, { header: headerLabels })
|
||||
|
||||
// 3) 列宽:根据表头和数据计算
|
||||
const cols = headers.map(h => {
|
||||
const headW = calcWch(h.label)
|
||||
const dataW = rows.reduce((mx, r) => Math.max(mx, calcWch(r[h.label])), 0)
|
||||
return { wch: Math.max(headW, dataW) }
|
||||
})
|
||||
ws['!cols'] = cols
|
||||
|
||||
// 4) 冻结首行、开启筛选
|
||||
if (ws['!ref']) {
|
||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 }
|
||||
ws['!autofilter'] = { ref: ws['!ref'] }
|
||||
}
|
||||
|
||||
// 5) 生成工作簿并下载
|
||||
const wb = XLSXUtils.book_new()
|
||||
XLSXUtils.book_append_sheet(wb, ws, sheetName)
|
||||
const safeName = filename.endsWith('.xlsx') ? filename : `${filename}.xlsx`
|
||||
XLSXWriteFile(wb, safeName)
|
||||
}).catch(error => {
|
||||
console.error('Failed to load xlsx library:', error)
|
||||
// 可以在这里添加错误提示
|
||||
})
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user