数据表

假设分析中的数据表用于在不同输入条件下批量展示公式结果,便于快速比较变量变化对计算结果的影响。

概述 本示例展示了 SpreadJS 中假设分析数据表(Data Table)的加载、计算与文件导入导出流程。 页面左侧显示一个预置的数据表工作簿,右侧提供导入/导出面板、计算模式切换和手动计算按钮,便于观察数据表在不同计算模式下的行为,并验证数据表内容在不同文件格式之间的保存与恢复效果。 实现思路 页面加载后创建 Workbook,并绑定状态栏组件。 通过 spread.fromJSON(datatable) 直接载入预置的数据表工作簿内容。 右侧面板提供导入功能,支持 .sjs、.ssjson、.json 和 .xlsx 文件,并统一采用增量加载和重新计算配置。 导出功能允许把当前工作簿导出为 SSJSON、SJS 或 Excel (XLSX) 格式。 计算模式支持“自动 / 部分 / 手动”三种切换方式,用来观察数据表在不同计算策略下的刷新行为。 用户可以点击“立即计算”按钮手动触发一次完整计算,并在结果区域查看操作反馈。 代码解析 示例入口先创建工作簿实例,然后调用 initSpread 完成数据加载和事件绑定: 初始化时,代码并没有在 app.js 中手动拼装数据表,而是直接从外部的 datatable 对象恢复完整工作簿内容。这意味着示例的主要重点是“如何承载并操作一个已有的数据表模型”: 导入逻辑支持两条路径:如果文件是 .sjs,就调用 spread.open;否则走 spread.import。两种方式都复用同一套选项,确保加载后执行完整重算并启用增量计算: 导出逻辑则根据右侧下拉框决定输出格式。对于 sjs 使用 spread.save,对于 ssjson 和 xlsx 使用 spread.export: 计算模式切换是这个 demo 的另一个重点。示例通过单选按钮修改 spread.options.calculationMode,并分别处理三种模式: 其中“部分”模式不会直接执行完整计算,而是通过挂起并恢复计算服务来刷新除数据表之外的公式。这也是示例里专门区分三种计算方式的原因。 如果用户处于手动模式或希望强制刷新结果,还可以点击“立即计算”按钮主动触发一次 spread.calculate(): 运行效果 页面打开后会自动加载一个预置的数据表工作簿,并在左侧工作表区域显示出来。 右侧“导入 / 导出”面板可以把当前数据表另存为不同格式,也可以重新导入外部文件验证恢复效果。 切换“自动 / 部分 / 手动”计算模式后,结果区域会即时提示当前状态。 点击“立即计算”后,工作簿会执行一次显式计算,并显示完成或失败信息。 导入或导出成功后,结果区域会显示短暂的成功提示;发生异常时则显示错误信息。 API 参考 spread.fromJSON(datatable) 用于从预置 JSON 数据恢复整个工作簿内容。 spread.open(file, success, error, options) / spread.import(file, success, error, options) 分别用于打开 SJS 文件和导入其他电子表格文件。 spread.save(...) / spread.export(...) 用于把当前工作簿导出为不同格式。 spread.options.calculationMode 用于切换自动、部分和手动三种计算模式。 spread.calculate() 用于主动触发一次完整计算。
var spread; window.onload = function () { spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss")); initSpread(spread); }; function isSJSFile(file) { var fileName = file.name.toLowerCase(); return fileName.endsWith('.sjs'); } function initSpread(spread) { var statusBar = new GC.Spread.Sheets.StatusBar.StatusBar(document.getElementById('statusBar')); statusBar.bind(spread); spread.fromJSON(datatable); // 从 datatable.js 导入 // 导入文件 document.getElementById("importBtn").addEventListener('click', function() { document.getElementById("importFile").click(); }); document.getElementById("importFile").addEventListener('change', function(e) { var resultDiv = document.getElementById("result"); var file = e.target.files[0]; if (!file) return; var options = { openMode: 2 /* 增量加载 */, fullRecalc: true, dynamicReferences: false, incrementalCalculation: true }; try { if (isSJSFile(file)) { spread.open(file, function() { resultDiv.innerHTML = '<div class="success">文件导入成功!</div>'; setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); }, function(error) { resultDiv.innerHTML = '<div class="error">导入文件时出错:' + (error.errorMessage || error.message) + '</div>'; }, options); } else { spread.import(file, function() { resultDiv.innerHTML = '<div class="success">文件导入成功!</div>'; setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); }, function(error) { resultDiv.innerHTML = '<div class="error">导入文件时出错:' + (error.errorMessage || error.message) + '</div>'; }, options); } } catch (error) { resultDiv.innerHTML = '<div class="error">导入文件时出错:' + error.message + '</div>'; } // 重置输入 e.target.value = ''; }); // 导出文件 document.getElementById("exportBtn").addEventListener('click', function() { var resultDiv = document.getElementById("result"); var format = document.getElementById("exportFormat").value; try { var fileName = "export." + format; if (format === "sjs") { spread.save(function(blob) { saveAs(blob, fileName); resultDiv.innerHTML = '<div class="success">文件导出成功!</div>'; setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); }, function(error) { resultDiv.innerHTML = '<div class="error">导出文件时出错:' + (error.errorMessage || error.message) + '</div>'; }); } else { var options = { fileType: format === "ssjson" ? GC.Spread.Sheets.FileType.ssjson : GC.Spread.Sheets.FileType.excel }; spread.export(function(blob) { saveAs(blob, fileName); resultDiv.innerHTML = '<div class="success">文件导出成功!</div>'; setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); }, function(error) { resultDiv.innerHTML = '<div class="error">导出文件时出错:' + (error.errorMessage || error.message) + '</div>'; }, options); } } catch (error) { resultDiv.innerHTML = '<div class="error">导出文件时出错:' + error.message + '</div>'; } }); // 计算模式切换 var calcModeRadios = document.querySelectorAll('input[name="calcMode"]'); calcModeRadios.forEach(function(radio) { radio.addEventListener('change', function() { var mode = parseInt(this.value); var resultDiv = document.getElementById("result"); spread.options.calculationMode = mode; if (mode === 0) { spread.calculate(); resultDiv.innerHTML = '<div class="success">计算模式已切换为:自动</div>'; } else if (mode === 2 /* partial */) { // this will calc formulas except for data table spread.suspendCalcService(); spread.resumeCalcService(); resultDiv.innerHTML = '<div class="success">计算模式已切换为:部分</div>'; } else if (mode === 1) { resultDiv.innerHTML = '<div class="success">计算模式已切换为:手动</div>'; } setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); }); }); // 立即计算按钮 document.getElementById("calculateBtn").addEventListener('click', function() { var resultDiv = document.getElementById("result"); try { spread.calculate(); resultDiv.innerHTML = '<div class="success">计算已完成!</div>'; setTimeout(function() { resultDiv.innerHTML = ''; }, 2000); } catch (error) { resultDiv.innerHTML = '<div class="error">计算时出错:' + error.message + '</div>'; } }); }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="spreadjs culture" content="zh-cn" /> <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$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-calc-worker/dist/gc.spread.sheets.calcworker.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-io/dist/gc.spread.sheets.io.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/data/datatable.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/FileSaver.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.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 class="spreadsheet-container"> <div id="ss" class="sample-spreadsheets"></div> <div id="statusBar" class="status-bar"></div> </div> <div class="options-container"> <div class="option-row"> <label>导入 / 导出:</label> <input type="file" id="importFile" accept=".ssjson,.sjs,.json,.xlsx" style="display:none" /> <input type="button" value="导入文件" id="importBtn" /> <label for="exportFormat" style="margin-top: 8px;">导出格式:</label> <select id="exportFormat"> <option value="ssjson">SSJSON</option> <option value="sjs">SJS</option> <option value="xlsx">Excel (XLSX)</option> </select> <input type="button" value="导出文件" id="exportBtn" /> </div> <div class="option-row"> <label>计算模式:</label> <div class="radio-group"> <label class="radio-label"> <input type="radio" name="calcMode" value="0" checked /> 自动 </label> <label class="radio-label"> <input type="radio" name="calcMode" value="2" /> 部分 </label> <label class="radio-label"> <input type="radio" name="calcMode" value="1" /> 手动 </label> </div> <input type="button" value="立即计算" id="calculateBtn" /> </div> <div class="option-row"> <label>结果:</label> <div id="result" class="result-container"></div> </div> </div> </div> </body> </html>
.sample-tutorial { position: relative; height: 100%; overflow: hidden; } .spreadsheet-container { width: calc(100% - 280px); height: 100%; float: left; display: flex; flex-direction: column; } .sample-spreadsheets { flex: 1; overflow: hidden; } .status-bar { height: 30px; border-top: 1px solid #ccc; } .options-container { float: right; width: 280px; overflow: auto; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; } .option-row { margin-bottom: 12px; } .option-row label { display: block; margin-bottom: 4px; font-weight: bold; font-size: 12px; } .description { font-size: 12px; color: #555; line-height: 1.4; margin: 0 0 8px 0; } .description-small { font-size: 11px; color: #666; line-height: 1.3; margin: 0 0 6px 0; } select { width: 100%; padding: 6px; margin-bottom: 6px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box; font-size: 12px; } input[type=button] { width: 100%; padding: 8px 6px; margin-bottom: 6px; background: #007acc; color: white; border: none; border-radius: 3px; cursor: pointer; font-weight: bold; } input[type=button]:hover { background: #005a9e; } input[type=button].secondary-button { background: #6c757d; } input[type=button].secondary-button:hover { background: #545b62; } .result-container { padding: 8px; border-radius: 3px; min-height: 40px; } .success { background: #d4edda; color: #155724; padding: 8px; border-radius: 3px; } .error { background: #f8d7da; color: #721c24; padding: 8px; border-radius: 3px; } .radio-group { margin-bottom: 8px; } .radio-label { display: block; font-weight: normal; font-size: 12px; margin-bottom: 4px; cursor: pointer; } .radio-label input[type="radio"] { margin-right: 6px; cursor: pointer; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; }