不止导入导出:用xlsx.mini.min.js在微信小程序里玩转Excel数据清洗与格式定制
微信小程序Excel高阶实战:用xlsx.mini.min.js实现数据清洗与样式定制
当业务人员将一份满是合并单元格、格式混乱的Excel表格扔过来时,大多数开发者第一反应可能是手动整理。但如果你正在开发微信小程序,其实可以用xlsx.mini.min.js这个轻量级库,将繁琐的数据清洗和报表生成变成自动化流程。
1. 从基础到进阶:重新认识xlsx.mini.min.js
很多开发者仅仅把xlsx.mini.min.js当作一个简单的导入导出工具,实际上它是SheetJS的精简版,保留了核心的Excel处理能力。与完整版相比,它去掉了部分边缘功能,但保留了:
- 完整的XLSX/XLS文件解析与生成
- 工作表与单元格级别的精细控制
- 丰富的数据转换选项
- 样式定制能力
安装建议:直接从SheetJS官方CDN获取最新版本,避免使用来路不明的副本。将文件放在小程序的libs目录后,通过以下方式引入:
const xlsx = require('../../libs/xlsx.mini.min')提示:虽然文件名为".mini.min",但功能并不"迷你"。它支持Excel 2007+的所有核心特性,包括现代格式如.xlsx。
2. 数据清洗:把"脏数据"变成结构化JSON
业务人员提供的Excel往往存在各种问题:表头不统一、合并单元格、多余空行等。下面介绍几种常见场景的处理方案。
2.1 处理不规则表头
当Excel表头可能出现在第2行或第3行时,使用header参数动态识别:
const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: detectHeaderRow(worksheet), // 自定义检测函数 defval: null // 空单元格设为null而非undefined }) function detectHeaderRow(ws) { const range = xlsx.utils.decode_range(ws['!ref']) for (let R = range.s.r; R <= range.e.r; ++R) { const cell = ws[xlsx.utils.encode_cell({c:0,r:R})] if (cell && cell.t === 's' && cell.v.includes('日期')) { return R // 返回包含"日期"的行号作为表头 } } return 1 // 默认第一行为表头 }2.2 处理合并单元格
合并单元格会破坏数据结构,需要先展开:
function expandMerges(data, merges) { merges.forEach(merge => { const start = xlsx.utils.decode_cell(merge.s) const end = xlsx.utils.decode_cell(merge.e) const value = data[start.r][start.c] for (let R = start.r; R <= end.r; ++R) { for (let C = start.c; C <= end.c; ++C) { if (!(R === start.r && C === start.c)) { data[R][C] = value } } } }) return data } // 使用示例 const worksheet = xlsx.utils.aoa_to_sheet(rawData) if (worksheet['!merges']) { rawData = expandMerges(rawData, worksheet['!merges']) }2.3 数据标准化处理
导入的数据往往需要类型转换和校验:
const standardized = jsonData.map(item => ({ date: formatExcelDate(item.日期), // Excel日期转JS日期 amount: parseFloat(item.金额) || 0, department: item.部门?.trim() || '未分配' }))3. 样式定制:生成专业级报表
基础的导出功能只能生成"裸"数据表格,而业务往往需要符合公司规范的报表样式。
3.1 精细控制列宽与行高
通过!cols和!rows实现像素级控制:
ws['!cols'] = [ { wpx: 120 }, // 第一列120像素 { wch: 15 }, // 第二列15字符宽度 { width: 30 } // 第三列自动宽度 ] ws['!rows'] = [ { hpx: 40 }, // 表头行高40px { hpt: 20 } // 数据行高20磅 ]注意:微信小程序中建议使用像素(px)而非字符宽度(wch),因为不同设备渲染效果可能不同。
3.2 条件格式模拟
虽然xlsx.mini.min.js不支持真正的条件格式,但可以通过预处理实现类似效果:
function addColorFormat(sheet, data) { data.forEach((row, rowIndex) => { row.forEach((cell, colIndex) => { if (cell > 1000) { // 值大于1000的单元格标红 const cellRef = xlsx.utils.encode_cell({c: colIndex, r: rowIndex}) sheet[cellRef].s = { fill: { patternType: 'solid', fgColor: { rgb: 'FFFF0000' } }, font: { bold: true } } } }) }) }3.3 多工作表报表
生成包含多个关联工作表的复杂报表:
const wb = xlsx.utils.book_new() // 添加数据表 xlsx.utils.book_append_sheet(wb, dataSheet, "销售数据") // 添加汇总表 const summarySheet = xlsx.utils.json_to_sheet(summaryData) xlsx.utils.book_append_sheet(wb, summarySheet, "月度汇总") // 添加图表工作表 const chartSheet = xlsx.utils.aoa_to_sheet(chartData) xlsx.utils.book_append_sheet(wb, chartSheet, "趋势分析")4. 性能优化与实战技巧
处理大型Excel文件时,需要特别注意内存和性能问题。
4.1 流式处理大文件
对于超过2MB的文件,建议分块处理:
function processLargeFile(base64Data, chunkSize = 500) { const workbook = xlsx.read(base64Data, { type: 'base64' }) const sheet = workbook.Sheets[workbook.SheetNames[0]] const jsonData = xlsx.utils.sheet_to_json(sheet, { header: 1 }) for (let i = 0; i < jsonData.length; i += chunkSize) { const chunk = jsonData.slice(i, i + chunkSize) // 处理当前数据块 processChunk(chunk) // 释放内存 if (i % 1000 === 0) { wx.nextTick(() => {}) } } }4.2 缓存优化
频繁导出的场景可以使用缓存:
let reportCache = null function generateReport(data) { if (!reportCache) { const ws = xlsx.utils.json_to_sheet(data) ws['!cols'] = COLUMN_CONFIG const wb = xlsx.utils.book_new() xlsx.utils.book_append_sheet(wb, ws, "Report") reportCache = xlsx.write(wb, { type: 'base64', bookType: 'xlsx' }) } return reportCache }4.3 错误处理与日志
完善的错误处理能提升用户体验:
async function exportWithRetry(data, retries = 3) { try { return await exportExcel(data) } catch (error) { if (retries > 0) { await new Promise(resolve => setTimeout(resolve, 1000)) return exportWithRetry(data, retries - 1) } logError(error) throw new Error('导出失败,请稍后重试') } } function logError(error) { const logs = wx.getStorageSync('export_logs') || [] logs.push({ time: new Date().toISOString(), error: error.message }) wx.setStorageSync('export_logs', logs.slice(-50)) // 保留最近50条 }5. 实战案例:销售报表系统
结合上述技术,我们实现了一个完整的销售报表处理流程:
数据导入阶段:
- 自动识别不同分公司的表格模板
- 清洗合并单元格和空行
- 统一日期和金额格式
数据处理阶段:
- 按产品线分类汇总
- 计算环比增长率
- 标记异常数据
报表导出阶段:
- 生成带公司logo的页眉
- 重点数据高亮显示
- 自动调整列宽适应内容
关键代码片段:
// 生成带样式的最终报表 function generateFinalReport(data) { const ws = xlsx.utils.json_to_sheet(data) // 设置样式 ws['!cols'] = REPORT_COLUMN_STYLES addHeaderStyle(ws) addDataBars(ws, data) // 添加页眉页脚 ws['!headerFooter'] = { oddHeader: '&C&"Calibri,Bold"&14销售报表', oddFooter: '&R页码 &P / &N' } return xlsx.write( xlsx.utils.book_append_sheet( xlsx.utils.book_new(), ws, `${new Date().getMonth()+1}月报表` ), { type: 'base64' } ) }在实际项目中,这套方案将原本需要2小时的手工报表整理工作缩短到3分钟自动完成。业务人员上传原始数据后,小程序自动处理并生成符合财务规范的报表,大大提升了工作效率。
