| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- import LuckyExcel from 'luckyexcel'
- import * as XLSX from 'xlsx'
- /**
- * 修复luckysheet中的公式解析问题
- * 主要解决以下问题:
- * 1. Excel公式 =IF(E6=100%,"Y",N) 被luckyexcel解析为 =IF(E6=100%,"Y",_xleta.N) 的问题
- * 2. 百分比格式问题:将100%转换为数值1
- * 3. 计算链缺失问题:确保所有包含公式的单元格都被正确添加到calcChain中
- * 4. 通用公式语法修复
- *
- * @param {Array} sheetData - luckysheet数据
- * @returns {Array} 修复后的数据
- */
- export function fixLuckySheetFormulas(sheetData) {
- try {
- if (!sheetData || !Array.isArray(sheetData)) return sheetData
-
- // 遍历所有sheet
- return sheetData.map(sheet => {
- if (!sheet) return sheet
-
- // 第一步:修复公式语法问题(百分比格式、_xleta前缀等)
- sheet = fixFormulaSyntax(sheet)
-
- // 第二步:修复计算链,确保所有公式单元格都能正常计算
- sheet = fixCalcChain(sheet)
-
- return sheet
- })
- } catch (error) {
- return sheetData
- }
- }
- /**
- * 修复公式语法问题
- * 包括:_xleta前缀修复、百分比格式转换等
- *
- * @param {Object} sheet - 单个工作表数据
- * @returns {Object} 修复后的工作表
- */
- function fixFormulaSyntax(sheet) {
- if (!sheet) return sheet
-
- // 修复celldata中的公式
- if (Array.isArray(sheet.celldata)) {
- sheet.celldata.forEach(cell => {
- if (cell && cell.v && cell.v.f) {
- const formula = cell.v.f
- let newFormula = formula
-
- // 修复 _xleta.N 问题
- // 检查公式模式: =IF(E6=100%,"Y",_xleta.N)
- // 修复为: =IF(E6=100%,"Y","N")
-
- // 更精确的匹配:只匹配在函数参数位置且不在函数名位置的_xleta.N
- const regex = /(IF\([^,]+,[^,]+,)_xleta\.(N)(\))/g
- const matches = [...formula.matchAll(regex)]
-
- if (matches.length > 0) {
- matches.forEach(match => {
- const before = match[1]
- const variable = match[2]
- const after = match[3]
-
- // 对于字母N、Y等,作为字符串处理
- const isSimpleLetter = /^[A-Za-z]$/.test(variable)
- if (isSimpleLetter) {
- // 对于简单字母变量,替换为字符串
- const replacement = `${before}"${variable}"${after}`
- newFormula = newFormula.replace(match[0], replacement)
- }
- })
- }
-
- // 更通用的修复:处理其他_xleta前缀的变量
- // 例如: _xleta.Y, _xleta.VAR 等
- const genericRegex = /_xleta\.([A-Za-z]+)/g
- const genericMatches = [...newFormula.matchAll(genericRegex)]
-
- if (genericMatches.length > 0) {
- genericMatches.forEach(match => {
- const fullMatch = match[0]
- const variableName = match[1]
-
- // 判断是否为简单变量名(单个字母)
- if (/^[A-Za-z]$/.test(variableName)) {
- newFormula = newFormula.replace(fullMatch, `"${variableName}"`)
- }
- })
- }
-
- // 智能修复百分比格式问题
- // 对于公式 =IF(E6=100%,"Y","N"),需要将100%转换为luckysheet能理解的格式
- // 同时需要处理小数百分比,如 =IF(E15<=0.01%,"Y","N"),应该转换为 =IF(E15<=0.0001,"Y","N")
-
- // 第一步:先修复可能存在的重复小数点问题(如0.0.01应该修正为0.0001)
- // 这种问题可能发生在luckyexcel解析过程中
- // 问题:0.01% 被错误解析为 0.0.01,实际上应该是 0.0001
- const duplicateDotRegex = /(\d+)\.(\d+)\.(\d+)/g
- const duplicateDotMatches = [...newFormula.matchAll(duplicateDotRegex)]
-
- if (duplicateDotMatches.length > 0) {
- duplicateDotMatches.forEach(match => {
- const fullMatch = match[0]
- const integerPart = match[1] // 如 "0"
- const firstDecimalPart = match[2] // 如 "0"
- const secondDecimalPart = match[3] // 如 "01"
-
- // 处理常见的百分比解析错误模式
- // 1. 0.01% 被解析为 0.0.01,应该修正为 0.0001
- // 2. 0.1% 被解析为 0.0.1,应该修正为 0.001
- // 3. 0.001% 被解析为 0.0.001,应该修正为 0.00001
- // 经过分析发现规律:0.0.xxx 模式通常表示 xxx/100 的百分比
- // 例如:0.0.01 表示 0.01% = 0.0001,0.0.1 表示 0.1% = 0.001
-
- let correctNumber
- if (integerPart === '0' && firstDecimalPart === '0') {
- // 针对 0.0.xxx 模式的特殊修复
- // 规则:转换为 0.00xxx,其中 xxx 是 secondDecimalPart
- // 例如:0.0.01 → 0.0001,0.0.1 → 0.001,0.0.001 → 0.00001
- correctNumber = parseFloat(`0.00${secondDecimalPart}`)
- } else {
- // 其他情况:使用更保守的修复
- // 将 X.Y.Z 合并为 X.YZ(假设第一个小数部分是整数部分的小数)
- correctNumber = parseFloat(`${integerPart}.${firstDecimalPart}${secondDecimalPart}`)
- }
-
- newFormula = newFormula.replace(fullMatch, correctNumber.toString())
- })
- }
-
- // 第二步:处理百分比转换为数值的问题
- // 支持整数和小数百分比,如 100%、0.01%、1.5%
- const percentRegex = /(\d+(?:\.\d+)?)%/g
- const percentMatches = [...newFormula.matchAll(percentRegex)]
-
- if (percentMatches.length > 0) {
- percentMatches.forEach(match => {
- const fullMatch = match[0]
- const number = match[1]
- const percentValue = parseFloat(number) / 100
-
- // 将百分比转换为数值,因为Excel中100%就是数值1,0.01%就是0.0001
- newFormula = newFormula.replace(fullMatch, percentValue.toString())
- })
- }
-
- if (newFormula !== formula) {
- cell.v.f = newFormula
- }
- }
- })
- }
-
- // 修复data数组中的公式
- if (Array.isArray(sheet.data)) {
- sheet.data.forEach((row, rowIndex) => {
- if (Array.isArray(row)) {
- row.forEach((cell, colIndex) => {
- if (cell && cell.f) {
- const formula = cell.f
- let newFormula = formula
-
- // 修复 _xleta.N 问题(与上面相同的逻辑)
- const regex = /(IF\([^,]+,[^,]+,)_xleta\.(N)(\))/g
- const matches = [...formula.matchAll(regex)]
-
- if (matches.length > 0) {
- matches.forEach(match => {
- const before = match[1]
- const variable = match[2]
- const after = match[3]
-
- const isSimpleLetter = /^[A-Za-z]$/.test(variable)
- if (isSimpleLetter) {
- const replacement = `${before}"${variable}"${after}`
- newFormula = newFormula.replace(match[0], replacement)
- }
- })
- }
-
- // 通用修复
- const genericRegex = /_xleta\.([A-Za-z]+)/g
- const genericMatches = [...newFormula.matchAll(genericRegex)]
-
- if (genericMatches.length > 0) {
- genericMatches.forEach(match => {
- const fullMatch = match[0]
- const variableName = match[1]
-
- if (/^[A-Za-z]$/.test(variableName)) {
- newFormula = newFormula.replace(fullMatch, `"${variableName}"`)
- }
- })
- }
-
- // 智能修复百分比格式问题
- // 处理整数和小数百分比,如 100% 转换为 1,0.01% 转换为 0.0001
-
- // 第一步:先修复可能存在的重复小数点问题(如0.0.01应该修正为0.0001)
- const duplicateDotRegex = /(\d+)\.(\d+)\.(\d+)/g
- const duplicateDotMatches = [...newFormula.matchAll(duplicateDotRegex)]
-
- if (duplicateDotMatches.length > 0) {
- duplicateDotMatches.forEach(match => {
- const fullMatch = match[0]
- const integerPart = match[1] // 如 "0"
- const firstDecimalPart = match[2] // 如 "0"
- const secondDecimalPart = match[3] // 如 "01"
-
- // 处理常见的百分比解析错误模式
- let correctNumber
- if (integerPart === '0' && firstDecimalPart === '0') {
- // 针对 0.0.xxx 模式的特殊修复
- // 规则:转换为 0.00xxx,其中 xxx 是 secondDecimalPart
- correctNumber = parseFloat(`0.00${secondDecimalPart}`)
- } else {
- // 其他情况:使用更保守的修复
- correctNumber = parseFloat(`${integerPart}.${firstDecimalPart}${secondDecimalPart}`)
- }
-
- newFormula = newFormula.replace(fullMatch, correctNumber.toString())
- })
- }
-
- // 第二步:处理百分比转换为数值的问题
- const percentRegex = /(\d+(?:\.\d+)?)%/g
- const percentMatches = [...newFormula.matchAll(percentRegex)]
-
- if (percentMatches.length > 0) {
- percentMatches.forEach(match => {
- const fullMatch = match[0]
- const number = match[1]
- const percentValue = parseFloat(number) / 100
-
- // 将百分比转换为数值
- newFormula = newFormula.replace(fullMatch, percentValue.toString())
- })
- }
-
- if (newFormula !== formula) {
- cell.f = newFormula
- }
- }
- })
- }
- })
- }
-
- return sheet
- }
- /**
- * 修复计算链(calcChain)
- * 问题描述:某些包含公式的单元格没有被正确添加到calcChain中,导致公式不计算
- * 例如:G11和H11公式工作,但I11及以后的公式不工作
- *
- * 解决方案:
- * 1. 遍历所有包含公式的单元格(包括celldata和data数组)
- * 2. 检查这些单元格是否已经在calcChain中
- * 3. 如果不在,则添加到calcChain中
- * 4. 确保计算链的正确性,使所有公式都能正常计算
- *
- * @param {Object} sheet - 单个工作表数据
- * @returns {Object} 修复后的工作表
- */
- function fixCalcChain(sheet) {
- if (!sheet) return sheet
-
- // 确保calcChain存在
- if (!sheet.calcChain) {
- sheet.calcChain = []
- }
-
- // 创建现有计算链的快速查找映射
- // 使用 "行_列" 作为键,例如 "10_6" 表示第11行G列
- const existingCalcMap = new Map()
- sheet.calcChain.forEach(item => {
- if (item && typeof item.r === 'number' && typeof item.c === 'number') {
- const key = `${item.r}_${item.c}`
- existingCalcMap.set(key, true)
- }
- })
-
- /**
- * 辅助函数:添加单元格到计算链
- * @param {number} row - 行索引(0-based)
- * @param {number} col - 列索引(0-based)
- */
- const addToCalcChain = (row, col) => {
- const key = `${row}_${col}`
-
- // 如果已经存在,跳过
- if (existingCalcMap.has(key)) {
- return
- }
-
- // 添加到计算链
- sheet.calcChain.push({
- r: row,
- c: col,
- index: sheet.index || "0" // 使用工作表索引,默认为"0"
- })
-
- // 更新映射
- existingCalcMap.set(key, true)
- }
-
- // 第一步:遍历celldata,查找有公式的单元格
- if (Array.isArray(sheet.celldata)) {
- sheet.celldata.forEach(cell => {
- if (cell && typeof cell.r === 'number' && typeof cell.c === 'number') {
- // 检查是否有公式
- const hasFormula = cell.v && cell.v.f
- if (hasFormula) {
- addToCalcChain(cell.r, cell.c)
- }
- }
- })
- }
-
- // 第二步:遍历data数组,查找有公式的单元格
- if (Array.isArray(sheet.data)) {
- sheet.data.forEach((row, rowIndex) => {
- if (Array.isArray(row)) {
- row.forEach((cell, colIndex) => {
- if (cell && cell.f) {
- addToCalcChain(rowIndex, colIndex)
- }
- })
- }
- })
- }
-
- // 第三步:处理特殊情况 - 检查是否有合并单元格影响公式计算
- // 如果单元格是合并单���格的一部分,可能需要特殊处理
- if (sheet.config && sheet.config.merge) {
- // 可以在这里添加合并单元格的特殊处理逻辑
- // 例如:确保合并单元格的主单元格在计算链中
- }
-
- return sheet
- }
- /**
- * 处理.xls文件导入
- * @param {ArrayBuffer} arrayBuffer - .xls文件的ArrayBuffer
- * @returns {Promise} 返回包含luckysheet数据的Promise
- */
- export function handleXlsFile(arrayBuffer) {
- return new Promise((resolve, reject) => {
- try {
- // 使用 xlsx 读取 .xls,保留单元格样式
- const workbook = XLSX.read(arrayBuffer, {
- type: 'array',
- cellStyles: true // 保留样式信息
- })
-
- // 转换为 .xlsx 格式的 ArrayBuffer,保留样式
- const xlsxArrayBuffer = XLSX.write(workbook, {
- bookType: 'xlsx',
- type: 'array',
- cellStyles: true, // 保留样式
- bookSST: false
- })
-
- // 用 LuckyExcel 解析转换后的数据
- handleXlsxFile(xlsxArrayBuffer)
- .then(resolve)
- .catch(reject)
-
- } catch (error) {
- reject(new Error('导入失败:.xls 文件格式不支持或已损坏!'))
- }
- })
- }
- /**
- * 处理.xlsx文件导入
- * @param {ArrayBuffer} arrayBuffer - .xlsx文件的ArrayBuffer
- * @returns {Promise} 返回包含luckysheet数据的Promise
- */
- export function handleXlsxFile(arrayBuffer) {
- return new Promise((resolve, reject) => {
- LuckyExcel.transformExcelToLucky(arrayBuffer, (exportJson, luckysheetfile) => {
- if (exportJson.sheets && exportJson.sheets.length > 0) {
- // 修复导入后的公式问题
- const fixedSheets = fixLuckySheetFormulas(exportJson.sheets)
- resolve(fixedSheets)
- } else {
- reject(new Error('导入失败:Excel 文件为空或格式错误!'))
- }
- })
- })
- }
- /**
- * 导入Excel文件的主函数
- * @param {File} file - Excel文件
- * @returns {Promise} 返回包含luckysheet数据的Promise
- */
- export async function importExcelFile(file) {
- return new Promise((resolve, reject) => {
- const fileName = file.name
- const fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase()
-
- if (fileExt !== '.xlsx' && fileExt !== '.xls') {
- reject(new Error('仅支持导入 .xlsx 和 .xls 格式的 Excel 文件!'))
- return
- }
-
- const reader = new FileReader()
-
- reader.onload = (evt) => {
- try {
- if (fileExt === '.xls') {
- // .xls 文件先用 xlsx 转换为 .xlsx
- handleXlsFile(evt.target.result)
- .then(resolve)
- .catch(reject)
- } else {
- // .xlsx 文件直接用 LuckyExcel 解析
- handleXlsxFile(evt.target.result)
- .then(resolve)
- .catch(reject)
- }
- } catch (error) {
- reject(new Error(`导入失败:${error.message || '文件解析错误,请检查文件格式是否正确'}`))
- }
- }
-
- reader.onerror = () => {
- reject(new Error('导入失败:文件读取错误!'))
- }
-
- reader.readAsArrayBuffer(file)
- })
- }
- /**
- * 获取导入文件的格式提示
- * @param {string} fileExt - 文件扩展名
- * @returns {string} 提示信息
- */
- export function getFormatTip(fileExt) {
- if (fileExt === '.xls') {
- return '.xls 格式导入时可能无法保留单元格样式(如颜色、边框等)。建议将文件另存为 .xlsx 格式后再导入,以完整保留样式。是否继续导入?'
- }
- return ''
- }
- export default {
- fixLuckySheetFormulas,
- handleXlsFile,
- handleXlsxFile,
- importExcelFile,
- getFormatTip
- }
|