Files
login_task_web/docs/excel导出.md

3.5 KiB
Raw Permalink Blame History

要真·生成 Excel.xlsx并避免中文乱码最简单稳妥的做法是用 SheetJS (xlsx)。xlsx 本身就是 Unicode不需要再加 BOM中文不会乱码。

1) 安装依赖

npm i xlsx

2) 在你的工具函数里引入并替换 exportToExcel

把你原来的 exportToExcel(写 application/vnd.ms-excel 的 HTML 方案)换成下面这个基于 xlsx 的实现——它会按 headers 的顺序与标题导出为真正的 .xlsx 文件,并自动设置列宽、表头冻结、筛选,同时做了“公式注入”防护。

// 顶部添加
import { utils as XLSXUtils, writeFile as XLSXWriteFile } from 'xlsx'

// 计算列宽(中文按双字节处理)
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 字符宽
}

// 简单日期格式化(如果字段是 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') {
  // 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)
}

3) 用法保持不变

const headers = [
  { label: '链接编号', key: 'codeNo' },
  { label: '状态',     key: 'status' },
  { label: '到期时间', key: 'expiredAt' },
  { label: '备注',     key: 'remark' },
]

exportToExcel(listData, headers, '链接列表.xlsx')

说明

  • 现在导出的是 真正的 .xlsx 文件Excel/Numbers/WPS 打开都不会中文乱码。
  • 不再需要 BOM、小心 Excel 旧版 HTML 方案的各种兼容问题。
  • 已对以 = + - @ 开头的字符串做了前缀 ' 处理,避免“公式注入”。
  • 如需多表导出,可多次 book_append_sheet 追加不同工作表。