概述
本 Demo 展示了 LET 函数在 SpreadJS 中的多种用法,包括基本用法、变量作用域规则、嵌套场景以及如何简化复杂公式。Demo 通过 5 个工作表演示了 LET 函数在实际业务场景中的应用,如计算员工在职时长、数据筛选和日期序列生成。
实现思路
启用动态数组模式(spread.options.allowDynamicArray = true),支持 LET 函数的高级特性
创建 5 个工作表,分别展示不同的 LET 函数用法
定义命名样式(Named Styles),用于统一的单元格格式展示
在"用例"工作表中结合表格绑定和 LET 函数实现实际的业务计算
在其他工作表中通过公式示例展示 LET 的语法规则和作用域特性
代码解析
启用动态数组模式
LET 函数的某些高级特性(如数组遍历)需要启用动态数组模式。这行代码在初始化时设置 SpreadJS 实例的 allowDynamicArray 选项。
在表格中使用 LET 函数计算在职时长
这行代码为表格的第 3 列设置计算公式。LET 函数定义变量 time 存储工作年限,然后通过 SWITCH 函数返回相应的时长描述。这种方式避免了重复计算 YEAR(TODAY())-YEAR([@[入职日期]]),提高了公式可读性。
结合下拉框实现动态聚合计算
这个公式使用 LET 定义了三个变量:data(薪资列数据)、calc(用户选择的计算类型)、aggregate(通过 CHOOSE 映射的 SUBTOTAL 函数代码),最后使用 SUBTOTAL 函数执行聚合计算。当用户通过下拉框改变 C9 单元格的值时,公式会自动切换计算最小值、最大值或平均值。
LET 局部变量优先级演示
这段代码演示了 LET 局部变量的优先级规则。工作表定义了自定义名称 user(值为 "Michael"),但在 LET 函数内部定义了同名变量 user(值为 "Ivy"),公式结果会使用 LET 函数中的局部变量,显示 "实际用户是: Ivy"。
使用 LET 简化数据筛选
这个公式展示了 LET 如何简化复杂公式。通过将筛选条件 filterCriteria、筛选结果 filteredRange 定义为变量,公式的逻辑变得清晰易读:先筛选数据,再判断是否为空,最后返回结果或显示 "-"。
生成工作日序列
这个公式使用 LET 定义 dates 变量存储日期序列,然后通过 FILTER 函数过滤出工作日(WEEKDAY < 6)。LET 函数使公式更易理解,避免了在 FILTER 中重复编写 SEQUENCE 表达式。
运行效果
"用例"工作表:显示技术支持工程师表格,"在职时长"列自动计算并显示工作年限范围;点击"薪资计算"区域的下拉框可选择最小值、最大值或平均值,右侧单元格实时显示计算结果
"#1"工作表:展示 LET 函数的基本语法,包括单变量对、多变量对、调用公式和动态数组示例,每个示例都显示公式文本和计算结果
"#2"工作表:演示 LET 局部变量与自定义名称的作用域规则,当存在同名变量时,LET 局部变量优先
"#3"工作表:展示 LET 嵌套时的作用域规则,内层 LET 优先使用当前作用域的变量
"#4"工作表:提供两个实际案例 - 根据输入动态筛选人员信息、生成指定日期范围内的工作日列表
API 参考
LET 函数语法
name1:第一个要分配的变量名称。必须以字母开头,不能是公式的输出结果或与区域的语法冲突
name_value1:分配给 name1 的值,可以是静态值、表达式或函数引用
calculation_or_name2:最终计算表达式(使用前面定义的变量),或者是第二个变量的名称
name_value2:可选,分配给第二个变量的值
LET 函数最多支持 126 个名称/值对
allowDynamicArray 选项
启用动态数组模式,支持 LET 函数的数组遍历特性。
setColumnDataFormula 方法
为表格列设置计算公式,columnIndex 为列索引,formula 为公式字符串。
window.onload = function() {
var spread = new GC.Spread.Sheets.Workbook(_getElementById("ss"));
spread.options.allowDynamicArray = true;
initStyles(spread);
initSpread(spread);
};
function initSpread(spread) {
spread.setSheetCount(5);
spread.suspendPaint();
spread.suspendCalcService();
initSheet1(spread.getSheet(0));
initSheet2(spread.getSheet(1));
initSheet3(spread.getSheet(2));
initSheet4(spread.getSheet(3));
initSheet5(spread.getSheet(4));
spread.resumeCalcService();
spread.resumePaint();
}
function initStyles(spread) {
var introStyle = new GC.Spread.Sheets.Style();
introStyle.name = 'intro';
introStyle.font = 'normal bold 16px Segoe UI';
introStyle.foreColor = "#172b4d";
spread.addNamedStyle(introStyle);
var introSecStyle = new GC.Spread.Sheets.Style();
introSecStyle.name = 'introSec';
introSecStyle.font = 'normal bold 12px Segoe UI';
introSecStyle.foreColor = "#000";
spread.addNamedStyle(introSecStyle);
var introGrayStyle = new GC.Spread.Sheets.Style();
introGrayStyle.name = 'introGray';
introGrayStyle.font = 'normal bold 12px Segoe UI';
introGrayStyle.foreColor = "gray";
spread.addNamedStyle(introGrayStyle);
var introStyle1 = new GC.Spread.Sheets.Style();
introStyle1.name = 'intro1';
introStyle1.font = 'normal bold 14px Calibri';
introStyle1.hAlign = 0;
introStyle1.vAlign = 1;
introStyle1.foreColor = "#172b4d";
spread.addNamedStyle(introStyle1);
var formulaStyle = new GC.Spread.Sheets.Style();
formulaStyle.name = 'formula';
formulaStyle.font = 'normal bold 12px Consolas';
formulaStyle.foreColor = "#c00000";
introStyle1.vAlign = 1;
spread.addNamedStyle(formulaStyle);
var tableHeaderStyle = new GC.Spread.Sheets.Style();
tableHeaderStyle.name = 'tableHeader';
tableHeaderStyle.font = "normal bold 14.7px Calibri";
tableHeaderStyle.hAlign = 1;
tableHeaderStyle.backColor = "#d9e1f2";
spread.addNamedStyle(tableHeaderStyle);
var tableContentStyle = new GC.Spread.Sheets.Style();
tableContentStyle.name = 'tableContent';
tableContentStyle.font = "normal normal 14.7px Calibri";
tableContentStyle.hAlign = 1;
spread.addNamedStyle(tableContentStyle);
var sourceStyle = new GC.Spread.Sheets.Style();
sourceStyle.name = 'source';
sourceStyle.hAlign = 0;
sourceStyle.backColor = "#fce8ce";
spread.addNamedStyle(sourceStyle);
var resultStyle = new GC.Spread.Sheets.Style();
resultStyle.name = 'result';
resultStyle.hAlign = 0;
resultStyle.backColor = "#e2efda";
spread.addNamedStyle(resultStyle);
}
function initSheet1(sheet) {
sheet.name('用例');
var table1Source = {
name: '技术支持工程师在职时长',
data: [
{ engineer: 'Bob', start: new Date(2014,4,25), salary: 2790 },
{ engineer: 'Jim', start: new Date(2019,6,20), salary: 2216 },
{ engineer: 'Kevin', start: new Date(2017,2,1), salary: 2498 },
{ engineer: 'Sarah', start: new Date(2020,6,14), salary: 1989 }
]
};
sheet.addSpan(1, 1, 1, 4);
sheet.setValue(1, 1, table1Source.name);
sheet.getCell(1, 1).hAlign(1).font("normal bold 15px Calibri");
sheet.setColumnWidth(1, 132);
sheet.setColumnWidth(2, 87);
sheet.setColumnWidth(3, 147);
sheet.setColumnWidth(4, 63);
var table1 = sheet.tables.add('Table1', 2, 1, 5, 4);
table1.style(GC.Spread.Sheets.Tables.TableThemes.medium7);
var table1Column1 = new GC.Spread.Sheets.Tables.TableColumn(1, "engineer", "技术支持工程师");
var table1Column2 = new GC.Spread.Sheets.Tables.TableColumn(2, "start", "入职日期");
var table1Column3 = new GC.Spread.Sheets.Tables.TableColumn(3, null, "在职时长");
var table1Column4 = new GC.Spread.Sheets.Tables.TableColumn(4, "salary", "薪资");
table1.autoGenerateColumns(false);
table1.bind([table1Column1, table1Column2, table1Column3, table1Column4], 'data', table1Source);
table1.setColumnDataFormula(2, '=LET(time,(YEAR(TODAY())-YEAR([@[入职日期]])),SWITCH(TRUE,time>5,"5年以上",time>=1,"1-4年",time=0,"≤ 1年"))');
var style = new GC.Spread.Sheets.Style();
style.backColor = 'rgb(112,173,71)';
style.foreColor = 'rgb(255,255,255)';
style.font = 'normal bold 14px Calibri';
sheet.setStyle(8, 1, style);
sheet.setValue(8, 1, '薪资计算');
sheet.getCell(8, 2).backColor('rgb(227,239,218)');
sheet.getCell(8, 3).backColor('rgb(227,239,218)');
var lineStyle = GC.Spread.Sheets.LineStyle.dotted;
var lineBorder = new GC.Spread.Sheets.LineBorder('rgb(143,193,104)', lineStyle);
var sheetArea = GC.Spread.Sheets.SheetArea.viewport;
sheet.getRange(8, 1, 1, 3).setBorder(lineBorder, { left: true, right: true, top: true, bottom: true }, sheetArea);
var combo = new GC.Spread.Sheets.CellTypes.ComboBox();
combo.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.value);
combo.items([
{ text: '最小值', value: 1 },
{ text: '最大值', value: 2 },
{ text: '平均值', value: 3 }
]);
sheet.setCellType(8, 2, combo, GC.Spread.Sheets.SheetArea.viewport);
sheet.setValue(8, 2, 3);
sheet.setFormula(8, 3, '=LET(data,Table1[薪资],calc,C9,aggregate,CHOOSE(calc,5,4,1),SUBTOTAL(aggregate, data))');
}
function initSheet2(sheet) {
sheet.name('#1');
sheet.setValue(1, 1, 'LET 基本用法');
sheet.setStyle(1, 1, 'intro');
// Sample pair varible
var formula = '=LET(x,2,x+3)';
sheet.setValue(3, 1, '示例变量对');
sheet.setStyle(3, 1, 'introSec');
sheet.setValue(4, 1, formula);
sheet.setStyle(4, 1, 'formula');
sheet.setValue(5, 1, '结果');
sheet.setStyle(5, 1, 'result');
sheet.setFormula(5, 2, formula);
// Multiple pair varibles
var formula = '=LET(x,1,y,2,z,3,x+y+z=x*y*z)';
sheet.setValue(7, 1, '多个变量对');
sheet.setStyle(7, 1, 'introSec');
sheet.setValue(8, 1, formula);
sheet.setStyle(8, 1, 'formula');
sheet.setValue(9, 1, '结果');
sheet.setStyle(9, 1, 'result');
sheet.setFormula(9, 2, formula);
// Invoked formula
var formula = '=LET(x,1,y,2,SUM(x,y))';
sheet.setValue(11, 1, '调用公式');
sheet.setStyle(11, 1, 'introSec');
sheet.setValue(12, 1, formula);
sheet.setStyle(12, 1, 'formula');
sheet.setValue(13, 1, '结果');
sheet.setStyle(13, 1, 'result');
sheet.setFormula(13, 2, formula);
// Dynamic Array
var formula = '=LET(rows,0,cols,0,OFFSET(C18:E18,rows,cols))';
sheet.setValue(15, 1, '动态数组');
sheet.setStyle(15, 1, 'introSec');
sheet.setValue(16, 1, formula);
sheet.setStyle(16, 1, 'formula');
sheet.setValue(17, 1, '范围');
sheet.setStyle(17, 1, 'source');
sheet.setArray(17, 2, [[1,2,3]]);
sheet.setValue(18, 1, '结果');
sheet.setStyle(18, 1, 'result');
sheet.setFormula(18, 2, formula);
}
function initSheet3(sheet) {
sheet.name('#2');
sheet.setValue(1, 1, 'LET 局部变量和自定义名称');
sheet.setStyle(1, 1, 'intro');
sheet.setValue(2, 1, '此工作表包含一个自定义名称 "user",其值为 "Michael"');
sheet.setStyle(2, 1, 'introGray');
sheet.addCustomName('user', '="Michael"');
sheet.setValue(3, 1, 'user');
sheet.setStyle(3, 1, 'source');
sheet.setFormula(3, 2, '=user');
// Always use let local varible first
var formula = '=LET(user,"Ivy","实际用户是: "&user)';
sheet.setValue(5, 1, '始终优先使用 LET 局部变量');
sheet.setStyle(5, 1, 'introSec');
sheet.setValue(6, 1, formula);
sheet.setStyle(6, 1, 'formula');
sheet.setValue(7, 1, '结果');
sheet.setStyle(7, 1, 'result');
sheet.setFormula(7, 2, formula);
// Use custom name if not avalible local varible
var formula = '=LET(user,user,"实际用户是: "&user)';
sheet.setValue(9, 1, '如果没有可用的局部变量,则使用自定义名称');
sheet.setStyle(9, 1, 'introSec');
sheet.setValue(10, 1, formula);
sheet.setStyle(10, 1, 'formula');
sheet.setValue(11, 1, '结果');
sheet.setStyle(11, 1, 'result');
sheet.setFormula(11, 2, formula);
}
function initSheet4(sheet) {
sheet.name('#3');
sheet.setValue(1, 1, 'LET 嵌套');
sheet.setStyle(1, 1, 'intro');
// Always use the current scope varible
var formula = '=LET(var,"第一个作用域",LET(var,"第二个作用域",var))';
sheet.setValue(3, 1, '始终使用当前作用域的变量');
sheet.setStyle(3, 1, 'introSec');
sheet.setValue(4, 1, formula);
sheet.setStyle(4, 1, 'formula');
sheet.setValue(5, 1, '结果');
sheet.setStyle(5, 1, 'result');
sheet.setFormula(5, 2, formula);
// Use the top scope varible if not found the avalible varible in current scope
var formula = '=LET(var,"第一个作用域",LET(var,var,var&" [来自第二个作用域]"))';
sheet.setValue(7, 1, '如果当前作用域中没有可用的变量,则使用顶级作用域的变量');
sheet.setStyle(7, 1, 'introSec');
sheet.setValue(8, 1, formula);
sheet.setStyle(8, 1, 'formula');
sheet.setValue(9, 1, '结果');
sheet.setStyle(9, 1, 'result');
sheet.setFormula(9, 2, formula);
}
function initSheet5(sheet) {
sheet.name('#4');
sheet.setValue(1, 1, 'LET 简化复杂公式');
sheet.setStyle(1, 1, 'intro');
// Filter the data to show one person
var formula = '=LET(filterCriteria,H7,filteredRange,FILTER(B7:E13,B7:B13=filterCriteria),IF(ISBLANK(filteredRange),"-",filteredRange))';
sheet.setValue(3, 1, '筛选数据以显示一个人的信息');
sheet.setStyle(3, 1, 'introSec');
sheet.setValue(4, 1, formula);
sheet.setStyle(4, 1, 'formula');
var data = [
["代表", "区域", "产品", "利润"],
["Amy", "东部", "苹果", 1.33 ],
["Fred", "南部", "香蕉", 0.09],
["Amy", "西部", "芒果", 1.85],
["Fred", "北部", null, 0.82],
["Fred", "西部", "香蕉", 1.25],
["Amy", "东部", "苹果", 0.72],
["Fred", "北部", "芒果", 0.54]
];
sheet.setStyle(5, 1, 'tableHeader');
sheet.setStyle(5, 2, 'tableHeader');
sheet.setStyle(5, 3, 'tableHeader');
sheet.setStyle(5, 4, 'tableHeader');
sheet.setArray(5, 1, data);
sheet.setValue(6, 6, '代表');
sheet.setStyle(6, 6, 'source');
sheet.setValue(7, 6, '结果');
sheet.setStyle(7, 6, 'result');
sheet.setValue(6, 7, 'Fred');
sheet.setFormula(7, 7, formula);
// Generate all dates between May 1, 2020 and May 15, 2020
sheet.setColumnWidth(2, 72);
sheet.setColumnWidth(7, 72);
var formula = '=LET(dates,SEQUENCE(C19-C18+1,1,C18,1),FILTER(dates,WEEKDAY(dates,2)<6))';
var formatter = '[$-en-US]dd-mmm-yy;@';
sheet.setValue(15, 1, '生成 2020 年 5 月 1 日至 2020 年 5 月 15 日之间的所有日期');
sheet.setStyle(15, 1, 'introSec');
sheet.setValue(16, 1, formula);
sheet.setStyle(16, 1, 'formula');
sheet.setValue(17, 1, '开始');
sheet.setStyle(17, 1, 'tableHeader');
sheet.setValue(18, 1, '结束');
sheet.setStyle(18, 1, 'tableHeader');
sheet.setValue(17, 2, new Date(2020, 4, 1));
sheet.setValue(18, 2, new Date(2020, 4, 15));
sheet.setFormatter(17, 2, formatter);
sheet.setFormatter(18, 2, formatter);
sheet.setValue(17, 6, '结果');
sheet.setStyle(17, 6, 'result');
sheet.setFormula(17, 7, formula);
for (var i = 0; i < 11; i ++) {
sheet.setFormatter(i + 17, 7, formatter);
}
}
function _getElementById(id) {
return document.getElementById(id);
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta charset="utf-8" />
<meta name="spreadjs culture" content="zh-cn" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css"
href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js"
type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js"
type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="sample-tutorial">
<div id="ss" class="sample-spreadsheets"></div>
</div>
</body>
</html>
input[type="text"] {
width: 200px;
margin-right: 20px;
}
label {
display: inline-block;
width: 110px;
}
.sample-tutorial {
position: relative;
height: 100%;
overflow: hidden;
}
.sample-spreadsheets {
width: 100%;
height: 100%;
overflow: hidden;
float: left;
}
label {
display: block;
margin-bottom: 6px;
}
input {
padding: 4px 6px;
}
input[type=button] {
margin-top: 6px;
display: block;
width:216px;
}
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
code {
border: 1px solid #000;
}