/** * 将列索引 (0, 1, 2...) 转换为字母 (A, B, C...) */ export const indexToColName = (index) => { let label = '' let i = index while (i >= 0) { label = String.fromCharCode(65 + (i % 26)) + label i = Math.floor(i / 26) - 1 } return label } /** * 将坐标 (A1) 解析为 {r, c} */ export const parseCellCoord = (coord) => { const match = coord.match(/^([A-Z]+)([0-9]+)$/) if (!match) return null const colStr = match[1] const rowStr = match[2] let c = 0 for (let i = 0; i < colStr.length; i++) { c = c * 26 + (colStr.charCodeAt(i) - 64) } return { r: parseInt(rowStr) - 1, c: c - 1 } } /** * 函数注册表:包含实现逻辑、描述和语法提示 * 用于公式计算和 UI 提示 */ export const FUNCTION_REGISTRY = { // --- 数学与统计 --- SUM: { description: '计算单元格区域中所有数值的和', syntax: 'SUM(number1, [number2], ...)', impl: (...args) => { return args.flat(Infinity).reduce((a, b) => { const num = Number(b) return isNaN(num) ? a : a + num }, 0) } }, AVERAGE: { description: '返回参数的平均值(算术平均值)', syntax: 'AVERAGE(number1, [number2], ...)', impl: (...args) => { const nums = args.flat(Infinity).map(Number).filter(n => !isNaN(n)) if (nums.length === 0) return 0 return nums.reduce((a, b) => a + b, 0) / nums.length } }, MAX: { description: '返回一组值中的最大值', syntax: 'MAX(number1, [number2], ...)', impl: (...args) => { const nums = args.flat(Infinity).map(Number).filter(n => !isNaN(n)) return nums.length ? Math.max(...nums) : 0 } }, MIN: { description: '返回一组值中的最小值', syntax: 'MIN(number1, [number2], ...)', impl: (...args) => { const nums = args.flat(Infinity).map(Number).filter(n => !isNaN(n)) return nums.length ? Math.min(...nums) : 0 } }, COUNT: { description: '计算区域中包含数字的单元格个数', syntax: 'COUNT(value1, [value2], ...)', impl: (...args) => { return args.flat(Infinity).filter(v => typeof v === 'number' || (typeof v === 'string' && !isNaN(Number(v)) && v.trim() !== '')).length } }, ROUND: { description: '将数字四舍五入到指定的位数', syntax: 'ROUND(number, num_digits)', impl: (number, digits = 0) => { const n = Number(number) const d = Number(digits) if (isNaN(n) || isNaN(d)) return '#VALUE!' return Math.round(n * (10 ** d)) / (10 ** d) } }, ABS: { description: '返回数字的绝对值', syntax: 'ABS(number)', impl: (number) => Math.abs(Number(number)) }, // --- 逻辑 --- IF: { description: '判断是否满足某个条件,如果满足返回一个值,如果不满足返回另一个值', syntax: 'IF(logical_test, value_if_true, [value_if_false])', impl: (condition, trueVal, falseVal) => { return condition ? trueVal : (falseVal === undefined ? false : falseVal) } }, AND: { description: '如果所有参数都为 TRUE,则返回 TRUE', syntax: 'AND(logical1, [logical2], ...)', impl: (...args) => args.flat(Infinity).every(Boolean) }, OR: { description: '如果任一参数为 TRUE,则返回 TRUE', syntax: 'OR(logical1, [logical2], ...)', impl: (...args) => args.flat(Infinity).some(Boolean) }, // --- 文本 --- CONCAT: { description: '将两个或多个文本字符串联接为一个字符串', syntax: 'CONCAT(text1, [text2], ...)', impl: (...args) => args.flat(Infinity).join('') }, LEN: { description: '返回文本字符串中的字符个数', syntax: 'LEN(text)', impl: (text) => String(text || '').length }, TRIM: { description: '移除文本中多余的空格', syntax: 'TRIM(text)', impl: (text) => String(text || '').trim() }, UPPER: { description: '将文本转换为大写', syntax: 'UPPER(text)', impl: (text) => String(text || '').toUpperCase() }, LOWER: { description: '将文本转换为小写', syntax: 'LOWER(text)', impl: (text) => String(text || '').toLowerCase() }, // --- 日期与时间 --- NOW: { description: '返回当前日期和时间', syntax: 'NOW()', impl: () => new Date().toLocaleString() }, TODAY: { description: '返回当前日期', syntax: 'TODAY()', impl: () => new Date().toLocaleDateString() } } /** * 简易公式求值器 * @param {String} formula 公式字符串 (e.g. "=SUM(A1:B2) + 10") * @param {Function} getValueCb 回调函数,用于获取指定单元格的值 (r, c) => val */ export const evaluateFormula = (formula, getValueCb) => { if (!formula || typeof formula !== 'string' || !formula.startsWith('=')) return formula const expression = formula.substring(1).trim() if (!expression) return '' // 1. 预处理范围引用:将 A1:B2 转换为数值数组 [1, 2, 3, 4] // 注意:为了支持文本函数,如果单元格内容是文本,也需要正确提取 const rangeRegex = /([A-Z]+[0-9]+):([A-Z]+[0-9]+)/g const parsedExpressionWithRanges = expression.replace(rangeRegex, (match, start, end) => { const s = parseCellCoord(start) const e = parseCellCoord(end) if (!s || !e) return match const values = [] const minR = Math.min(s.r, e.r), maxR = Math.max(s.r, e.r) const minC = Math.min(s.c, e.c), maxC = Math.max(s.c, e.c) for(let r=minR; r<=maxR; r++) { for(let c=minC; c<=maxC; c++) { let val = getValueCb(r, c) // 尝试转为数字,如果不仅是数字字符则保留原值(用于文本处理) const numVal = Number(val) if (!isNaN(numVal) && val !== '') { values.push(numVal) } else { // 简单的转义处理,防止字符串破坏数组结构 values.push(`"${String(val).replace(/"/g, '\\"')}"`) } } } return `[${values.join(',')}]` }) // 2. 预处理单个单元格引用:将 A1 转换为具体值 // 复杂的正则用于避免匹配到已经是字符串中的内容,这里做简化处理 // 我们假设单元格引用不会出现在引号内部(这是一个简易引擎的限制) const cellRegex = /(? { const coords = parseCellCoord(match) if (!coords) return 0 const val = getValueCb(coords.r, coords.c) const numVal = Number(val) // 如果是纯数字,返回数字;否则返回带引号的字符串 return (!isNaN(numVal) && val !== '') ? numVal : `"${String(val).replace(/"/g, '\\"')}"` }) // 3. 构建执行上下文 // 将 FUNCTION_REGISTRY 中的 impl 提取出来作为参数传递给 Function 构造器 const funcNames = Object.keys(FUNCTION_REGISTRY) const funcImpls = funcNames.map(name => FUNCTION_REGISTRY[name].impl) try { // 注入所有支持的函数到执行作用域 // e.g. new Function('SUM', 'AVERAGE', ..., 'return SUM(1, 2)') const func = new Function(...funcNames, `return ${finalExpression}`) const result = func(...funcImpls) // 如果结果是 NaN 或 Infinity,做一些友好的处理 if (typeof result === 'number' && isNaN(result)) return '#VALUE!' if (typeof result === 'number' && !isFinite(result)) return '#DIV/0!' return result } catch (e) { console.warn(`Formula Eval Error: ${formula}`, e) return '#ERROR!' } }