|
|
@@ -0,0 +1,277 @@
|
|
|
+import LuckyExcel from 'luckyexcel'
|
|
|
+import * as XLSX from 'xlsx'
|
|
|
+
|
|
|
+/**
|
|
|
+ * 修复luckysheet中的公式解析问题
|
|
|
+ * 主要解决:Excel公式 =IF(E6=100%,"Y",N) 被luckyexcel解析为 =IF(E6=100%,"Y",_xleta.N) 的问题
|
|
|
+ * 同时修复百分比格式和公式语法问题
|
|
|
+ * @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
|
|
|
+
|
|
|
+ // 修复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能理解的格式
|
|
|
+ const percentRegex = /(\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
|
|
|
+ // 这是最可能工作的方案
|
|
|
+ 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}"`)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 智能修复百分比格式问题
|
|
|
+ const percentRegex = /(\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
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ return sheetData
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 处理.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
|
|
|
+}
|