Article

表格组件 SpreadJS

更新于:2026-07-09

第一章:SpreadJS 概述与环境搭建

1.1 什么是 SpreadJS

概念名称说明注意事项
SpreadJS纯前端 JavaScript 电子表格组件,由 GrapeCity 开发,支持类 Excel 功能,可在浏览器中实现数据展示、编辑、公式计算、导出等能力。不依赖服务器控件,完全运行在客户端,适合集成到 Vue、React、Angular 等框架中。
核心定位前端表格控件,提供高性能、高兼容性的电子表格解决方案。与 Excel 高度兼容,支持 .xlsx 文件导入导出。
技术栈基于 HTML5 Canvas 和 DOM 渲染结合,优化了大数据量下的渲染性能。默认使用 Canvas 渲染单元格内容以提升性能,可配置为 DOM 模式用于复杂交互。

1.2 SpreadJS 的核心特性与应用场景

特性名称说明注意事项
类 Excel 操作体验支持公式、格式化、排序、筛选、合并单元格、冻结窗格等。用户无需学习即可上手,降低培训成本。
多框架支持支持原生 JS、React、Vue、Angular、Web Components。需根据框架选择对应的封装包(如 @grapecity/spread-sheets-vue)。
数据绑定能力支持静态数据绑定和动态数据源(Observable、Ajax)。可与后端 API 集成实现 CRUD。
导入导出支持导入/导出 Excel (.xlsx) 文件。导出时需注意样式和公式兼容性。
扩展性强支持自定义单元格类型、插件、主题、右键菜单等。开发者可基于 API 实现个性化功能。
应用场景 - 数据录入系统构建表单式数据录入界面,支持校验和批量提交。适合财务、报表类系统。
应用场景 - 报表展示动态生成复杂报表,支持分页打印和导出。可结合模板引擎预设格式。
应用场景 - 在线协作编辑结合 WebSocket 实现多用户实时编辑(需自行实现同步逻辑)。注意并发冲突处理。

1.3 开发环境准备与项目初始化

步骤名称操作细节注意事项
安装 Node.js下载并安装 LTS 版本的 Node.js(≥14.x)确保 npm 命令可用
创建项目目录执行 mkdir my-spreadjs-app && cd my-spreadjs-app建议使用语义化命名
初始化项目执行 npm init -y 生成 package.json可跳过交互式配置
安装 Web 服务器推荐安装 http-server:npm install -g http-server用于本地测试
创建基础文件结构新建 index.htmlstyles.cssscript.js保持结构清晰便于维护
检查浏览器支持使用现代浏览器(Chrome、Edge、Firefox 最新版)不支持 IE9 以下

1.4 引入 SpreadJS(CDN 与 NPM 方式)

引入方式操作细节注意事项
CDN 方式<head> 中添加 <script><link> 标签确保网络可访问 CDN;生产环境建议使用固定版本 URL
NPM 安装执行:npm install @grapecity/spread-sheets推荐用于模块化项目
NPM 导入(ES6)在 JS 文件中 import需配置打包工具(Webpack/Vite)支持 CSS 加载
样式文件选择提供多种主题 CSS:excel2013white、dark、flat 等可根据 UI 风格选择
全局对象CDN 方式下,GC.Spread.Sheets 为全局对象不需要 import 即可使用

CDN 方式示例:

<script src="https://cdn.grapecity.com/spreadjs/hosted/js/spread.sheets.all.min.js"></script>
<link href="https://cdn.grapecity.com/spreadjs/hosted/css/spread.sheets.excel2013white.css" rel="stylesheet" />

NPM 导入(ES6)示例:

import * as GC from '@grapecity/spread-sheets';
import '@grapecity/spread-sheets/styles/gc.spread.sheets.css';

1.5 创建第一个 SpreadJS 工作表实例

方法名称语法用途代码示例注意事项
new GC.Spread.Sheets.Workbooknew GC.Spread.Sheets.Workbook(element, options)初始化 SpreadJS 实例并绑定到 DOM 元素const container = document.getElementById('ss');
const workbook = new GC.Spread.Sheets.Workbook(container, { sheetCount: 1 });
element 必须是存在的 DOM 元素;options.sheetCount 设置初始工作表数量
setActiveSheetIndexworkbook.setActiveSheetIndex(index)切换当前激活的工作表workbook.setActiveSheetIndex(0);索引从 0 开始,超出范围无效
getActiveSheetworkbook.getActiveSheet()获取当前活动工作表对象const sheet = workbook.getActiveSheet();后续操作多基于 sheet 对象进行

第二章:基础工作表操作

2.1 工作簿与工作表的基本概念

概念名称说明注意事项
工作簿(Workbook)相当于一个 Excel 文件,包含一个或多个工作表。一个页面可创建多个 Workbook 实例,但通常只用一个。
工作表(Worksheet)工作簿中的单个标签页,用于存储数据和样式。每个工作表有独立的行列结构和数据区域。
活动工作表(Active Sheet)当前用户正在操作的工作表。只能有一个活动工作表。
Sheet 名称每个工作表的唯一标识名称,默认为 “Sheet1”、“Sheet2” 等。名称不能重复,长度限制 1-31 字符,不能含 \/*?[] 等特殊字符。
Sheet 索引工作表在工作簿中的位置索引,从 0 开始。添加顺序决定索引位置。

2.2 添加、删除与切换工作表

方法名称语法用途代码示例注意事项
addSheetworkbook.addSheet(name, sheet)添加一个新的工作表const newSheet = new GC.Spread.Sheets.Worksheet('MySheet');
workbook.addSheet(1, newSheet);
第一个参数为插入位置索引;若省略 sheet 参数会自动创建新实例
removeSheetworkbook.removeSheet(name)删除指定名称的工作表workbook.removeSheet('MySheet');不能删除最后一个工作表;删除后索引会重新排列
removeSheetAtworkbook.removeSheetAt(index)删除指定索引位置的工作表workbook.removeSheetAt(1);索引越界将抛出错误
setActiveSheetNameworkbook.setActiveSheetName(name)切换当前活动工作表(通过名称)workbook.setActiveSheetName('Sheet2');名称不存在则无效
setActiveSheetIndexworkbook.setActiveSheetIndex(index)切换当前活动工作表(通过索引)workbook.setActiveSheetIndex(0);索引越界无效
getSheetNameworkbook.getSheetName(index)获取指定索引处的工作表名称const name = workbook.getSheetName(0);用于遍历所有工作表
getSheetFromNameworkbook.getSheetFromName(name)根据名称获取工作表对象const sheet = workbook.getSheetFromName('Data');名称不存在返回 null

2.3 设置工作表名称与可见性

方法名称语法用途代码示例注意事项
setActiveSheetNameworkbook.setActiveSheetName(newName)修改当前活动工作表的名称workbook.setActiveSheetName('Summary');名称必须唯一,否则失败
getActiveSheetNameworkbook.getActiveSheetName()获取当前活动工作表的名称const name = workbook.getActiveSheetName();常用于状态显示
setSheetVisibleworkbook.setSheetVisible(name, visible)设置指定工作表是否可见workbook.setSheetVisible('HiddenSheet', false);隐藏后仍可通过名称或索引访问数据
getSheetVisibleworkbook.getSheetVisible(name)获取指定工作表的可见状态const isVisible = workbook.getSheetVisible('Sheet1');返回布尔值
showSheetTabsworkbook.options.showSheetTabs = bool控制是否显示底部工作表标签栏workbook.options.showSheetTabs = false;默认为 true;设为 false 后用户无法手动切换

2.4 行列的基本操作(增删改查)

方法名称语法用途代码示例注意事项
addRowssheet.addRows(index, count)在指定位置插入行sheet.addRows(5, 2); // 插入2行插入后原有数据下移
addColumnssheet.addColumns(index, count)在指定位置插入列sheet.addColumns(3, 1); // 插入1列插入后原有数据右移
removeRowssheet.removeRows(index, count)删除指定位置的若干行sheet.removeRows(5, 2); // 删除2行删除后数据上移,不可撤销
removeColumnssheet.removeColumns(index, count)删除指定位置的若干列sheet.removeColumns(3, 1); // 删除1列删除后数据左移
getRowCountsheet.getRowCount()获取当前行数const rows = sheet.getRowCount();包括空行
getColumnCountsheet.getColumnCount()获取当前列数const cols = sheet.getColumnCount();包括空列
setRowCountsheet.setRowCount(count)设置总行数(扩展或截断)sheet.setRowCount(100);小于当前值会截断数据
setColumnCountsheet.setColumnCount(count)设置总列数sheet.setColumnCount(20);小于当前值会丢失列数据
getRowHeightsheet.getRowHeight(index)获取某行高度(像素)const height = sheet.getRowHeight(0);默认 22px
setRowHeightsheet.setRowHeight(index, height)设置某行高度sheet.setRowHeight(0, 30);支持自定义高度
getColumnWidthsheet.getColumnWidth(index)获取某列宽度const width = sheet.getColumnWidth(1);默认 74px
setColumnWidthsheet.setColumnWidth(index, width)设置某列宽度sheet.setColumnWidth(1, 100);支持调整列宽

2.5 单元格的基础赋值与读取

方法名称语法用途代码示例注意事项
setValuesheet.setValue(row, col, value)设置指定单元格的值sheet.setValue(0, 0, 'Hello');支持字符串、数字、布尔、日期等
getValuesheet.getValue(row, col)获取指定单元格的值const val = sheet.getValue(0, 0);若为空返回 null
setTextsheet.setText(row, col, text)设置单元格文本(保留格式)sheet.setText(0, 1, '123');即使是数字也作为文本处理
getTextsheet.getText(row, col)获取单元格显示文本const text = sheet.getText(0, 0);受格式影响,如日期显示为 “2025-01-01”
setFormulasheet.setFormula(row, col, formula)设置单元格公式sheet.setFormula(2, 0, '=A1+A2');支持内置函数如 SUM、IF 等
getFormulasheet.getFormula(row, col)获取单元格公式字符串const f = sheet.getFormula(2, 0);若无公式返回空字符串
clearCellsheet.clearCell(row, col, clearOption)清除单元格内容或格式sheet.clearCell(0, 0, GC.Spread.Sheets.SheetArea.viewport);clearOption 可选:all, content, format
isDirtysheet.isDirty()判断工作表是否有未保存的更改if (sheet.isDirty()) { ... }用于判断是否需要保存

第三章:单元格数据与格式设置

3.1 数据类型支持与设置

数据类型说明注意事项
字符串(String)默认类型,用于文本内容。可通过 setValue(row, col, "文本") 设置。
数值(Number)支持整数、浮点数,参与公式计算。输入时不要加引号,如 setValue(0, 0, 123.45)
布尔值(Boolean)true / false,可用于逻辑判断。在公式中常用于 IF、AND、OR 等函数。
日期时间(Date)JavaScript Date 对象,支持日期运算。推荐使用 new Date() 设置;显示格式可自定义。
空值(null / undefined)表示无数据。getValue() 返回 null 表示单元格为空。
错误值(Error)#DIV/0!, #VALUE! 等,由公式计算产生。不建议手动设置错误值。

3.2 字体、颜色与对齐方式设置

方法名称语法用途代码示例注意事项
fontsheet.getCell(row, col).font(fontString)设置单元格字体样式sheet.getCell(0, 0).font("bold 14px Arial");支持 CSS 字体语法:[bold/italic] size family
foreColorsheet.getCell(row, col).foreColor(color)设置字体颜色sheet.getCell(0, 0).foreColor("red");
// 或 "#FF0000"
接受颜色名称或十六进制值
backColorsheet.getCell(row, col).backColor(color)设置单元格背景色sheet.getCell(0, 0).backColor("#FFFFE0");优先级低于条件格式和填充样式
hAlignsheet.getCell(row, col).hAlign(hAlignment)设置水平对齐方式sheet.getCell(0, 0).hAlign(GC.Spread.Sheets.HorizontalAlign.center);可选值:left, center, right, general, justify
vAlignsheet.getCell(row, col).vAlign(vAlignment)设置垂直对齐方式sheet.getCell(0, 0).vAlign(GC.Spread.Sheets.VerticalAlign.middle);可选值:top, middle, bottom, baseline
wordWrapsheet.getCell(row, col).wordWrap(wrap)设置自动换行sheet.getCell(0, 0).wordWrap(true);需配合足够行高显示完整内容
textIndentsheet.getCell(row, col).textIndent(indent)设置文本缩进(空格数)sheet.getCell(0, 0).textIndent(2);仅影响显示,不改变实际值

3.3 边框样式配置

方法名称语法用途代码示例注意事项
borders.getBordersheet.borders.getBorder(row, col, borderType)获取指定边框对象const border = sheet.borders.getBorder(0, 0, GC.Spread.Sheets.BorderType.edgeBottom);用于读取当前边框样式
borders.setBordersheet.borders.setBorder(row, col, borderType, style)设置单元格边框sheet.borders.setBorder(0, 0, GC.Spread.Sheets.BorderType.all, new GC.Spread.Sheets.LineBorder("black", GC.Spread.Sheets.LineStyle.thin));borderType 可为 all, edgeLeft, edgeTop 等
LineBordernew GC.Spread.Sheets.LineBorder(color, lineStyle)创建边框线对象const line = new GC.Spread.Sheets.LineBorder("blue", GC.Spread.Sheets.LineStyle.medium);lineStyle 可选:thin, medium, thick, dash, dot 等
removeBordersheet.borders.removeBorder(row, col, borderType)移除指定边框sheet.borders.removeBorder(0, 0, GC.Spread.Sheets.BorderType.all);清除后恢复为无边框状态
setRangeBordersheet.setRangeBorder(row, col, rowCount, colCount, style, borderType)批量设置区域边框sheet.setRangeBorder(0, 0, 5, 3, new GC.Spread.Sheets.LineBorder("red", GC.Spread.Sheets.LineStyle.thick), GC.Spread.Sheets.BorderType.outline);borderType 使用 outline 可设置外框

3.4 背景填充与条件样式

方法名称语法用途代码示例注意事项
backgroundsheet.getCell(row, col).background(color)设置单元格背景色sheet.getCell(0, 0).background("#E6E6FA");与 backColor 功能相同
style.backColorsheet.setStyle(row, col, style) 结合 style.backColor批量设置样式对象const style = new GC.Spread.Sheets.Style();
style.backColor = "#D3D3D3";
sheet.setStyle(0, 0, style);
适用于复杂样式组合
conditionalFormats.addConditionsheet.conditionalFormats.addCondition(condition)添加条件格式规则见下文示例支持高亮、数据条、色阶、图标集等
HighlightCondition高亮条件对象设置基于值的背景/字体样式condition.style().foreColor = "white";常用于标记异常值
DataBar数据条条件显示条形图形式的数据比较const dataBar = new GC.Spread.Sheets.ConditionalFormatting.DataBar();
dataBar.color("lightblue");
sheet.conditionalFormats.addDataBar(0, 0, 10, 1, dataBar);
适用于数值列对比
ClearConditionsheet.conditionalFormats.clear()清除所有条件格式sheet.conditionalFormats.clear();可传入范围参数清除部分区域

条件格式示例:

const condition = new GC.Spread.Sheets.ConditionalFormatting.HighlightCondition();
condition.expected(">=100");
condition.style().backColor = "lightgreen";
sheet.conditionalFormats.addCondition(condition);

3.5 数字格式与日期格式化

方法名称语法用途代码示例注意事项
formattersheet.getCell(row, col).formatter(formatString)设置单元格格式字符串sheet.getCell(0, 0).formatter("0.00");控制显示格式,不影响实际值
内置数字格式"0"整数显示sheet.getCell(0, 0).formatter("0");四舍五入显示
小数格式"0.00"保留两位小数sheet.getCell(0, 0).formatter("0.00");不足补零
百分比格式"0.00%"百分比显示sheet.getCell(0, 0).formatter("0.00%");实际值需为 0~1 之间
货币格式"$#,##0.00"美元格式sheet.getCell(0, 0).formatter("$#,##0.00");支持千分位分隔符
日期格式 - 年月日"yyyy-MM-dd"标准日期sheet.getCell(0, 0).formatter("yyyy-MM-dd");配合 Date 类型使用
日期时间格式"yyyy-MM-dd HH:mm:ss"完整时间sheet.getCell(0, 0).formatter("yyyy-MM-dd HH:mm:ss");注意大小写:HH 24小时,hh 12小时
自定义格式"0元"添加单位sheet.getCell(0, 0).formatter("0元");支持文本拼接
getFormattersheet.getCell(row, col).formatter()获取当前格式字符串const fmt = sheet.getCell(0, 0).formatter();返回设置的格式模板

第四章:公式与计算引擎

4.1 内置函数概览

函数类别常用函数说明代码示例注意事项
数学函数SUM, AVERAGE, MIN, MAX, ROUND基础统计计算=SUM(A1:A10)支持区域引用
逻辑函数IF, AND, OR, NOT条件判断=IF(A1>10,"大","小")嵌套最多 64 层
文本函数CONCATENATE, LEFT, RIGHT, MID, LEN字符串处理=CONCATENATE(A1,B1)支持 ”&” 连接符
日期函数TODAY, NOW, YEAR, MONTH, DAY日期操作=TODAY()NOW() 包含时间
查找函数VLOOKUP, HLOOKUP, INDEX, MATCH数据查找=VLOOKUP(A1,D1:F10,2,FALSE)注意精确/模糊匹配
财务函数PMT, FV, PV贷款、现值计算=PMT(0.05/12, 360, 100000)参数顺序严格
工程函数DEC2BIN, HEX2DEC进制转换=DEC2BIN(10)支持多种进制
信息函数ISNUMBER, ISTEXT, ISBLANK类型检测=ISNUMBER(A1)返回 TRUE/FALSE

4.2 自定义公式注册与使用

方法名称语法用途代码示例注意事项
addFunctionGC.Spread.CalcEngine.addFunction(name, func, description, paramList)注册全局自定义函数GC.Spread.CalcEngine.addFunction("DOUBLE", function(val) { return val * 2; }, "将数值翻倍", ["number:要翻倍的数"]);必须在计算前注册
函数参数func(...args)接收公式传入的参数function(x, y) { return x + y; }支持可变参数
返回值return value返回计算结果return Math.sqrt(aa + bb);类型应与预期一致
使用自定义函数在单元格输入 =函数名(参数)调用已注册函数=DOUBLE(A1)不区分大小写
删除函数GC.Spread.CalcEngine.removeFunction(name)卸载自定义函数GC.Spread.CalcEngine.removeFunction("DOUBLE");一般不推荐运行时删除
异步函数支持不直接支持 async/await需通过回调或 Promise 封装不推荐用于实时计算场景可能导致重算延迟

4.3 公式依赖与重算机制

概念/方法说明代码示例注意事项
依赖关系当单元格 A 引用 B,则 A 依赖于 B=A1+1 中 A2 依赖 A1修改 A1 会触发 A2 重算
自动重算默认开启,数据变化时自动更新公式结果无需手动干预性能敏感场景可关闭
setCalcModeworkbook.options.calcMode = mode设置计算模式workbook.options.calcMode = GC.Spread.Sheets.CalcMode.manual;
recalcworkbook.recalc()手动触发全部重算workbook.recalc();
recalcCellsheet.recalcCell(row, col)重算指定单元格sheet.recalcCell(2, 0);
getFormulasheet.getFormula(row, col)获取公式字符串const f = sheet.getFormula(2, 0);
getDependentssheet.getDependents(row, col)获取依赖该单元格的所有单元格const deps = sheet.getDependents(0, 0);
getSiblingssheet.getSiblings(row, col)获取引用该单元格的公式单元格同 getDependents

4.4 错误处理与调试技巧

方法/技巧说明代码示例注意事项
常见错误类型#VALUE! (类型错误), #DIV/0! (除零), #REF! (引用无效), #NAME? (函数名错误)=1/0#DIV/0!观察错误提示定位问题
isErrorValueGC.Spread.CalcEngine.isErrorValue(value)判断值是否为错误类型if (GC.Spread.CalcEngine.isErrorValue(val)) { ... }
try-catch 包装在自定义函数中使用 try-catchtry { return x / y; } catch(e) { return "#ERROR!"; }防止崩溃,返回友好提示
getValue + 检查读取前判断是否为错误const v = sheet.getValue(0, 0);
if (v === null ...)
公式追踪使用开发者工具查看公式依赖无直接 API,需手动调试可打印 getFormula 和 getDependents
suspendCalculationsheet.suspendCalculation()暂停计算(批量操作优化)见下文示例
resumeCalculationsheet.resumeCalculation()恢复计算并触发重算同上
日志输出使用 console.log 输出中间值console.log("A1=", sheet.getValue(0,0));调试自定义函数时非常有用

批量操作优化示例:

sheet.suspendCalculation();
// 批量设值
sheet.resumeCalculation();

第五章:数据绑定与数据源集成

5.1 静态数据绑定(数组、对象)

方法名称语法用途代码示例注意事项
setArraysheet.setArray(row, col, dataArray)将二维数组数据批量写入工作表const data = [["姓名", "年龄"], ["张三", 25], ["李四", 30]];
sheet.setArray(0, 0, data);
dataArray 应为嵌套数组结构
getArraysheet.getArray(row, col, rowCount, colCount)从指定区域读取数据为二维数组const arr = sheet.getArray(0, 0, 3, 2);返回包含 null 的矩形数据块
setDataSourcesheet.setDataSource(source)绑定对象数组作为数据源const users = [{name: "A", age: 20}, {name: "B", age: 22}];
sheet.setDataSource(users);
自动映射属性到列
getDatasheet.getData()获取当前数据源(若已绑定)const src = sheet.getData();仅当使用 setDataSource 时有效
autoGenerateColumnssheet.options.autoGenerateColumns = bool是否根据数据源自动创建列头sheet.options.autoGenerateColumns = true;默认为 true;设为 false 可手动定义列
setColumnWidthsheet.setColumnWidth(col, width)配合数据绑定调整列宽sheet.setColumnWidth(0, 100);建议在绑定后设置以优化显示

5.2 动态数据绑定(Ajax、Promise)

方法名称语法用途代码示例注意事项
fetch + thenfetch(url).then(r => r.json()).then(data => ...)使用原生 Fetch API 获取数据fetch('/api/users').then(res => res.json()).then(data => sheet.setDataSource(data));推荐现代浏览器环境使用
jQuery.ajax$.ajax({url, success: function(data){...}})使用 jQuery 发起异步请求$.ajax({ url: '/api/data', success: function(d) { sheet.setDataSource(d); } });需引入 jQuery 库
axios.getaxios.get(url).then(...)使用 Axios 库获取数据axios.get('/api/report').then(response => { sheet.setDataSource(response.data); });支持 Promise,语法简洁
async/awaitconst data = await fetch(...).then(...)使用 async 函数简化异步逻辑见下文示例更清晰的控制流
setDataEmptysheet.setDataSource(null)清空数据前显示加载状态sheet.setDataSource(null);
// 显示"加载中"...
// 加载完成后重新绑定
避免残留旧数据
错误处理.catch(err => console.error(err))捕获网络或解析错误fetch('/api/data').then(r => r.json()).then(d => sheet.setDataSource(d)).catch(e => alert("加载失败"));必须处理异常防止崩溃

async/await 示例:

async function load() {
    const res = await fetch('/data');
    const data = await res.json();
    sheet.setDataSource(data);
}
load();

5.3 与后端服务集成(CRUD 操作)

操作类型实现方式代码示例注意事项
创建(Create)监听单元格变更并提交新增记录见下文示例建议添加本地临时 ID 标记新行
读取(Read)初始化时加载数据见下文示例可结合分页减少单次加载量
更新(Update)监听 CellValueChanged 并 PUT/PATCH见下文示例推荐按字段或整行更新
删除(Delete)提供删除按钮或右键菜单调用 DELETE见下文示例删除前应确认用户意图
批量保存收集所有变更后一次性提交见下文示例减少请求次数,提升性能
数据同步状态在单元格标记”已修改”、“保存中”sheet.getCell(row, 0).foreColor("blue");
// 保存成功后恢复黑色
提升用户体验和操作可见性

创建(Create)示例:

sheet.bind(GC.Spread.Sheets.Events.ValueChanged, (e, args) => {
    if (isNewRow(args.row)) {
        axios.post('/api/users', getValueRow(args.row))
            .then(res => updateWithServerId(args.row, res.data.id));
    }
});

读取(Read)示例:

function fetchData() {
    axios.get('/api/users').then(r => sheet.setDataSource(r.data));
}

更新(Update)示例:

sheet.bind(GC.Spread.Sheets.Events.CellValueChanged, (e, args) => {
    const row = args.row;
    const field = sheet.columns[args.col].name;
    const value = sheet.getValue(row, args.col);
    axios.patch(`/api/users/${getId(row)}`, { [field]: value });
});

删除(Delete)示例:

function deleteRow(id) {
    axios.delete(`/api/users/${id}`)
        .then(() => {
            const index = findRowIndexById(id);
            sheet.deleteRows(index, 1);
        });
}

批量保存示例:

function saveAllChanges() {
    const changes = getModifiedRows(sheet);
    return axios.post('/api/batch-update', changes);
}

5.4 使用 Data Source 对象管理复杂数据

类型/方法语法用途代码示例注意事项
BindingSourcenew GC.Spread.DataSource.BindingSource(data, schema)创建可观察的数据源对象const bs = new GC.Spread.DataSource.BindingSource(users, null);
sheet.setDataSource(bs);
支持双向绑定和变更通知
属性映射schema.columns[{ name, displayName, dataType }]定义列与数据字段的映射关系见下文示例可控制显示名称和类型
数据验证bs.validate() 或字段级 validator在提交前校验数据完整性bs.setItemValidator("email", function(val) { return val.includes("@"); });可自定义验证逻辑
change事件bs.bind("collectionchanged", handler)监听数据源增删改变化bs.bind("collectionchanged", (e, info) => { console.log("变更类型:", info.action); // add/remove/change });适用于审计日志或联动更新
getCurrentbs.getCurrent()获取当前选中行对应的数据对象const current = bs.getCurrent();需配合选择模式使用
addNew / endEditbs.addNew(); bs.endEdit();添加新记录并结束编辑bs.addNew();
// 设置默认值
bs.endEdit();
endEdit 触发 itemChanged 事件
数据过滤bs.filter = "age > 18"设置筛选条件bs.filter = `name like '%${keyword}%'`;支持类 SQL 表达式
数据排序bs.sort("name asc")设置排序规则bs.sort("age desc, name asc");可多字段排序

属性映射示例:

const schema = {
    columns: [
        { name: "name", displayName: "姓名", dataType: "string" },
        { name: "birth", displayName: "出生日期", dataType: "date" }
    ]
};

第六章:交互功能与用户操作

6.1 启用/禁用单元格编辑

方法名称语法用途代码示例注意事项
isEditablesheet.getCell(row, col).isEditable(bool)设置指定单元格是否可编辑sheet.getCell(0, 0).isEditable(false);优先级高于行/列设置
options.isProtectedsheet.options.isProtected = bool启用工作表保护(禁用编辑)sheet.options.isProtected = true;启用后所有单元格默认不可编辑
defaultStylesheet.setDefaultStyle(style)设置默认样式中的可编辑性见下文示例影响未单独设置的单元格
setColumnReadOnlysheet.columns[col].locked = bool批量设置某列是否只读sheet.columns[0].locked = true;需配合 isProtected=true 生效
setRowReadOnlysheet.rows[row].locked = bool批量设置某行是否只读sheet.rows[0].locked = true;锁定后用户无法修改该行内容
canUserEditFormulasheet.options.canUserEditFormula = bool控制是否允许用户编辑公式sheet.options.canUserEditFormula = false;防止误改关键计算逻辑
canUserDragFillsheet.options.canUserDragFill = bool是否允许拖动填充句柄sheet.options.canUserDragFill = false;常用于锁定模板区域

defaultStyle 示例:

const style = new GC.Spread.Sheets.Style();
style.cellType = GC.Spread.Sheets.CellTypes.text;
style.isReadOnly = true;
sheet.setDefaultStyle(style);

6.2 选择模式与范围控制

方法名称语法用途代码示例注意事项
selectionModesheet.options.selectionMode = mode设置选择模式sheet.options.selectionMode = GC.Spread.Sheets.SelectionMode.row;可选:none, cell, row, column, range
selectionUnitsheet.options.selectionUnit = unit设置选择单位sheet.options.selectionUnit = GC.Spread.Sheets.SelectionUnit.range;cell 或 range
setSelectionsheet.setSelection(row, col, rowCount, colCount)编程方式设置选区sheet.setSelection(1, 1, 3, 2);会触发 SelectionChanged 事件
getSelectionssheet.getSelections()获取当前所有选区const ranges = sheet.getSelections();返回 CellRange 数组
clearSelectionsheet.clearSelection()清除当前选择sheet.clearSelection();无参数,清除所有高亮
allowUserZoomsheet.options.allowUserZoom = bool是否允许用户缩放视图sheet.options.allowUserZoom = true;默认开启,影响选择区域显示
showResizeIndicatorsheet.options.showResizeIndicator = bool显示行列调整指示器sheet.options.showResizeIndicator = true;提升用户体验

6.3 剪切、复制、粘贴行为配置

方法名称语法用途代码示例注意事项
canUserCopysheet.options.canUserCopy = bool是否允许复制sheet.options.canUserCopy = false;禁用 Ctrl+C 和右键复制
canUserCutsheet.options.canUserCut = bool是否允许剪切sheet.options.canUserCut = false;禁用 Ctrl+X
canUserPastesheet.options.canUserPaste = bool是否允许粘贴sheet.options.canUserPaste = false;禁用 Ctrl+V
pasteSpecialsheet.paste(row, col, data, mode)控制粘贴内容类型sheet.paste(0, 0, clipboardData, GC.Spread.Sheets.ClipboardPasteOptions.values);可选:all, values, formats, formulas
getClipValuesheet.getClipValue()获取将要粘贴的数据const data = sheet.getClipValue();用于拦截或修改粘贴内容
setClipValuesheet.setClipValue(data)设置剪贴板数据(模拟复制)sheet.setClipValue("Hello");通常用于自动化测试
bind(Event.Copy)sheet.bind(GC.Spread.Sheets.Events.Copy, handler)监听复制事件sheet.bind("copy", (e, args) => { log("复制了数据"); });可阻止默认行为 args.cancel = true
bind(Event.Paste)sheet.bind(GC.Spread.Sheets.Events.Paste, handler)监听粘贴事件sheet.bind("paste", (e, args) => { if (!isAuthorized()) args.cancel = true; });实现权限控制

6.4 拖拽与填充句柄控制

方法名称语法用途代码示例注意事项
canUserDragDropsheet.options.canUserDragDrop = bool是否允许单元格拖拽移动sheet.options.canUserDragDrop = false;默认为 true
canUserDragFillsheet.options.canUserDragFill = bool是否允许拖动填充句柄(+号)sheet.options.canUserDragFill = false;关闭后无法快速填充序列
autoFillTypesheet.autoFill.type = type设置自动填充类型sheet.autoFill.type = GC.Spread.Sheets.AutoFillType.copy;可选:copy, series, format, values
autoFillDirectionsheet.autoFill.direction = dir设置填充方向限制sheet.autoFill.direction = GC.Spread.Sheets.AutoFillDirection.x;x: 水平, y: 垂直, both
bind(Event.DragDrop)sheet.bind("dragdrop", handler)监听拖拽事件sheet.bind("dragdrop", (e, args) => { console.log(args.fromRow, args.toRow); });可用于实现行排序
bind(Event.DragFill)sheet.bind("dragfill", handler)监听填充事件sheet.bind("dragfill", (e, args) => { if (args.fillType === "series") args.cancel = true; });可阻止特定填充行为

手动公式序列填充示例:

sheet.setValue(0, 0, 1);
for (let i = 1; i < 5; i++) {
    sheet.setFormula(i, 0, '=R[-1]C+1');
}

6.5 键盘导航与快捷键定制

方法名称语法用途代码示例注意事项
navigate (arrow keys)方向键默认支持单元格间移动内置行为
enterKeyActionsheet.options.enterKeyAction = action设置回车键移动方向sheet.options.enterKeyAction = GC.Spread.Sheets.EnterKeyAction.moveDown;可选:moveNone, moveUp, moveDown, moveLeft, moveRight, moveNext, movePrevious
tabKeyActionsheet.options.tabKeyAction = action设置 Tab 键行为sheet.options.tabKeyAction = GC.Spread.Sheets.EnterKeyAction.moveRight;控制表单式输入流
bind(Event.KeyDown)sheet.bind("keydown", handler)监听键盘按键事件sheet.bind("keydown", (e, args) => { if (args.key === "F2") editCell(); });args.key 获取按键名
bind(Event.KeyUp)sheet.bind("keyup", handler)监听按键释放辅助组合键判断
stopDefaultKeyBindingspread.commandManager().register()注册自定义命令替换默认快捷键GC.Spread.Commands.register("mySave", mySaveFunc);
spread.commandManager().register("ctrl+s", "mySave");
高级用法,覆盖 Ctrl+S
isEnterInEditModesheet.options.isEnterInEditMode = bool回车是否进入编辑模式sheet.options.isEnterInEditMode = true;类似 Excel 双击效果
scrollWithArrowKeyssheet.options.scrollWithArrowKeys = bool方向键是否触发滚动sheet.options.scrollWithArrowKeys = true;大表格中建议开启

第七章:高级功能模块

7.1 冻结窗格与分页设置

方法名称语法用途代码示例注意事项
setFrozenCountsheet.setFrozenCount(row, col)设置冻结行列数sheet.setFrozenCount(1, 1); // 冻结首行首列冻结线前的内容固定不动
getFrozenCountsheet.getFrozenCount()获取当前冻结数量const frozen = sheet.getFrozenCount();返回 {row: n, col: m}
frozenFromRow/frozenFromColsheet.frozenFromRow = 2; sheet.frozenFromCol = 1;冻结指定位置为起点更灵活的冻结控制
options.viewportMarginssheet.options.viewportMargins设置视口边距(分页打印用)sheet.options.viewportMargins = { top: 50, bottom: 50 };控制打印时每页内容高度
pageSetupsheet.pageSetup配置打印页面参数sheet.pageSetup.orientation = GC.Spread.Sheets.Print.Orientation.landscape;设置纸张方向、边距等
printTitleRowssheet.printTitleRows(start, end)设置每页重复打印的标题行sheet.printTitleRows(0, 0); // 每页打第一行适用于多页报表
printTitleColumnssheet.printTitleColumns(start, end)设置每页重复打印的标题列sheet.printTitleColumns(0, 0);常用于宽表格
pageCountsheet.getPageCount()获取分页总数(打印预览)const pages = sheet.getPageCount();需先设置 pageSetup

7.2 合并单元格与跨列显示

方法名称语法用途代码示例注意事项
addSpansheet.addSpan(row, col, rowCount, colCount)合并指定区域单元格sheet.addSpan(0, 0, 2, 3); // 2x3 区域只保留左上角单元格的值和样式
removeSpansheet.removeSpan(row, col)取消合并(需指定左上角)sheet.removeSpan(0, 0);拆分后其他格为空
isSpannedsheet.isSpanned(row, col)判断某单元格是否属于合并区域if (sheet.isSpanned(1, 1)) { ... }返回 boolean
getSpansheet.getSpan(row, col)获取该位置所属合并区域范围const range = sheet.getSpan(0, 0);返回 CellRange 对象
mergeCellssheet.mergeCells(row, col, rowCount, colCount)同 addSpan,语义更明确sheet.mergeCells(1, 1, 1, 4);功能完全相同
unmergeCellssheet.unmergeCells(row, col)同 removeSpansheet.unmergeCells(0, 0);语法糖
options.allowCellOverflowsheet.options.allowCellOverflow = bool是否允许内容溢出到邻近空单元格sheet.options.allowCellOverflow = true;类似 Excel 的”合并居中”显示效果

7.3 表格(Table)对象的创建与样式

方法名称语法用途代码示例注意事项
addTablesheet.tables.add(name, row, col, rowCount, colCount, tableStyle)创建表格区域sheet.tables.add("MyTable", 0, 0, 10, 5, GC.Spread.Sheets.Tables.TableThemes.medium9);自动启用筛选和样式
getTablesheet.tables.getTable(name)获取表格对象const table = sheet.tables.getTable("MyTable");用于后续操作
bindColumntable.bindColumn("field", bindingInfo)绑定字段与数据源属性table.bindColumn("name", { displayName: "姓名", formatter: "0.00" });支持格式化和显示名
showHeadertable.showHeader = bool是否显示表头table.showHeader = true;默认开启
showFiltertable.showFilter = bool是否显示筛选下拉table.showFilter = false;控制用户筛选权限
resizetable.resize(newRowCount, newColCount)调整表格大小table.resize(20, 6);自动扩展数据区域
deleteTablesheet.tables.remove(name)删除表格(保留数据)sheet.tables.remove("MyTable");数据仍保留在工作表中
tableStyletable.tableStyle = styleName更换表格主题样式table.tableStyle = GC.Spread.Sheets.Tables.TableThemes.light1;支持多种内置主题

7.4 分组与折叠(Grouping)

方法名称语法用途代码示例注意事项
groupRowssheet.groupRows(start, end)对指定行范围进行分组sheet.groupRows(2, 5); // 折叠第3~6行生成可折叠层级
groupColumnssheet.groupColumns(start, end)对指定列范围进行分组sheet.groupColumns(1, 3); // 折叠B:D列支持水平分组
ungroupRowssheet.ungroupRows(start, end)取消行分组sheet.ungroupRows(2, 5);恢复展开状态
ungroupColumnssheet.ungroupColumns(start, end)取消列分组sheet.ungroupColumns(1, 3);
collapseGroupsheet.collapseGroup(start, end, isRow)折叠指定分组sheet.collapseGroup(2, 5, true);手动控制展开/折叠
expandGroupsheet.expandGroup(start, end, isRow)展开指定分组sheet.expandGroup(2, 5, true);
isRowCollapsedsheet.isRowCollapsed(index)判断某行是否被折叠if (sheet.isRowCollapsed(3)) { ... }用于状态判断
options.showGroupOutlinesheet.options.showGroupOutline = bool是否显示分组轮廓线sheet.options.showGroupOutline = true;默认显示,可关闭

7.5 打印与导出 PDF/Excel

方法名称语法用途代码示例注意事项
printspread.print()调用浏览器打印对话框spread.print({ paperSize: GC.Spread.Sheets.Print.PaperKind.a4, printDirection: GC.Spread.Sheets.Print.PrintDirection.landscape });支持打印设置
exportToPdfGC.Spread.Sheets.PDF.export(spread, stream, options)导出为 PDFGC.Spread.Sheets.PDF.export(spread, stream, { scaleMode: GC.Spread.Sheets.Print.ScaleMode.fitWidth });需引入 @grapecity/spread-sheets-pdf
exportToImageGC.Spread.Sheets.Image.export(spread, options)导出为图片(PNG/JPG)适用于快照分享
saveWorkbookworkbook.save(fileName, options)保存为 Excel 文件workbook.save("report.xlsx", { fileType: GC.Spread.Excel.FileType.xlsx });需引入 @grapecity/spread-excelio
openWorkbookworkbook.open(file, options)从 Excel 文件加载数据workbook.open(file, { autoParseFormula: true }, (success) => { ... });支持 .xlsx 格式
PDF 导出选项options.pageSettings, scaleMode, margins控制 PDF 输出效果{ scaleMode: "fitWidth", showGridline: false }可隐藏网格线、调整缩放
ExcelIO 实例const excelIO = new GC.Spread.Excel.IO();用于更精细的 Excel 导入导出控制excelIO.save(workbook, function(data) { download(data, "book.xlsx"); }, function(err) { alert(err); });错误处理更完善

文件下载示例:

const blob = new Blob([data], { type: "application/octet-stream" });
saveAs(blob, "data.xlsx");
// 需引入 FileSaver.js

第八章:事件系统与自定义行为

8.1 常用事件监听(ValueChanged, EditEnd 等)

事件名称语法触发时机代码示例注意事项
ValueChangedsheet.bind(GC.Spread.Sheets.Events.ValueChanged, handler)单元格值发生改变时sheet.bind("valueChanged", (e, args) => { console.log(值从 ${args.oldValue} 变为 ${args.value}); });包括公式重算导致的值变
CellValueChangedsheet.bind("cellvaluechanged", handler)特定单元格内容被用户修改sheet.bind("cellvaluechanged", (e, args) => { if (args.row === 0 && args.col === 0) { /* 处理 */ } });仅响应直接输入,不包含公式
EditStartsheet.bind("editstart", handler)编辑模式开始(进入单元格编辑)sheet.bind("editstart", (e, args) => { highlightRelatedCells(args.row, args.col); });可用于高亮关联区域
EditEndsheet.bind("editend", handler)编辑完成并提交值后sheet.bind("editend", (e, args) => { if (args.isCancelled) return; validateInput(args.row, args.col, args.value); });isCancelled 判断是否按 Esc 取消
SelectionChangedsheet.bind("selectionchanged", handler)选区发生变化时sheet.bind("selectionchanged", (e, args) => { const range = args.newSelections[0]; updateStatusBar(range); });newSelections 为 CellRange 数组
RowHeightChangedsheet.bind("rowheightchanged", handler)行高调整后sheet.bind("rowheightchanged", (e, args) => { saveLayout(); });适用于保存自定义布局
ColumnWidthChangedsheet.bind("columnwidthchanged", handler)列宽调整后sheet.bind("columnwidthchanged", (e, args) => { console.log("列", args.col, "宽度:", args.newSize); });可用于响应式设计同步
SheetNameChangedworkbook.bind("sheetnamechanged", handler)工作表名称更改workbook.bind("sheetnamechanged", (e, args) => { log(工作表 ${args.oldName} → ${args.newName}); });跨 sheet 管理时有用

8.2 事件冒泡与阻止默认行为

概念/方法说明代码示例注意事项
事件冒泡机制SpreadJS 事件支持从单元格向工作表、工作簿逐层传播类似 DOM 事件模型
args.cancel = true阻止事件的默认行为sheet.bind("valueChanged", (e, args) => { if (!isValid(args.value)) { args.cancel = true; alert("输入无效!"); } });并非所有事件都可取消
args.handled = true标记事件已被处理,防止后续监听器执行sheet.bind("cellclick", (e, args) => { showTooltip(args.row, args.col); args.handled = true; });用于控制事件处理流程
多监听器顺序多个绑定按注册顺序执行A → B → C建议关键逻辑后注册
unbindsheet.unbind(event, handler)移除特定事件监听const handler = () => { ... };
sheet.bind("editend", handler);
sheet.unbind("editend", handler);
全局拦截在父级(如 workbook)监听并控制子级行为workbook.bind("valueChanged", (e, args) => { if (args.sheet.name === "Template") args.cancel = true; });实现权限或模板保护

8.3 自定义右键菜单

方法名称语法用途代码示例注意事项
contextMenu.setMenuItemscontextMenu.setMenuItems(items)设置右键菜单项列表见下文示例内置 command 可直接使用
bind(ContextMenuOpening)sheet.bind("contextmenuopening", handler)在菜单显示前动态修改sheet.bind("contextmenuopening", (e, args) => { const sel = sheet.getSelections()[0]; if (sel.rowCount > 1) { addMenuItem("批量处理"); } });可根据选区调整菜单
自定义命令GC.Spread.Commands.register(name, exec)注册可调用的命令GC.Spread.Commands.register("customSave", { execute: () => saveToServer() });
// 菜单中使用: { text: "保存", command: "customSave" }
实现业务逻辑封装
显示/隐藏菜单项menuItem.visible = bool动态控制菜单项可见性item.visible = userHasPermission();提升安全性
禁用菜单项menuItem.enabled = bool控制是否可点击item.enabled = sheet.getSelections().length > 0;常用于上下文敏感操作
完全替换菜单contextMenu.show = false + 自定义 UI使用自建菜单替代默认菜单spread.contextMenu.show = false;
document.addEventListener("contextmenu", showCustomMenu);
更灵活但需自行管理定位与事件

右键菜单示例:

const menu = spread.contextMenu;
menu.setMenuItems([
    { text: "复制", command: "Copy" },
    { text: "清除", command: "Clear" },
    { text: "-", }, // 分隔符
    { text: "计算总和", name: "sum" }
]);

8.4 单元格双击/单击行为扩展

事件语法用途代码示例注意事项
CellClicksheet.bind("cellclick", handler)监听单元格单击sheet.bind("cellclick", (e, args) => { if (args.button === 0) { navigateToDetail(args.row); } });button: 0=左键, 2=右键
CellDoubleClicksheet.bind("celldoubleclick", handler)监听单元格双击sheet.bind("celldoubleclick", (e, args) => { startInlineEditor(args.row, args.col); });常用于触发编辑或弹窗
isDoubleClickEditsheet.options.isDoubleClickEdit = bool是否允许双击进入编辑模式sheet.options.isDoubleClickEdit = false;关闭后可自定义双击行为
自定义弹窗结合 cellDoubleClick 打开模态框sheet.bind("celldoubleclick", (e, args) => { showModal({ data: sheet.getRowData(args.row) }); });适用于查看详情或复杂编辑
导航跳转单击特定列触发页面跳转sheet.bind("cellclick", (e, args) => { if (args.col === 0) openProfile(args.row); });提升交互效率
展开/折叠行双击分组行实现折叠切换sheet.bind("celldoubleclick", (e, args) => { if (isGroupHeader(args.row)) { toggleGroup(args.row); } });增强数据组织体验

第九章:性能优化与大规模数据处理

9.1 虚拟滚动与渲染优化

方法/配置语法说明代码示例注意事项
virtualization内置机制仅渲染可视区域内的单元格SpreadJS 默认启用虚拟滚动
viewportRowssheet.options.viewportRows设置视口中显示的行数sheet.options.viewportRows = 20;影响滚动性能与内存占用
viewportColumnssheet.options.viewportColumns设置视口中显示的列数sheet.options.viewportColumns = 15;宽表格需合理设置
setRowHeight / setColWidthsheet.setRowHeight(row, height)避免频繁动态调整行列高宽for (let i = 0; i < 10000; i++) { sheet.setRowHeight(i, 25); }动态变化会降低渲染性能
disable animationssheet.options.animation = false关闭动画效果提升流畅度sheet.options.animation = false;大数据量时建议关闭
optimize cell types减少复杂 cell type 使用如避免全表使用 ComboBox简单文本渲染最快

9.2 大数据量加载策略

策略实现方式优点注意事项
分页加载每次只加载一页数据(如 100 行)内存占用低,启动快需实现”加载更多”按钮或滚动触底加载
懒加载(Lazy Load)滚动到某区域时再加载数据用户感知流畅需监听滚动事件并预测加载时机
数据切片(Chunking)将大数据拆分为多个块异步写入避免主线程阻塞使用 setTimeout 或 requestIdleCallback
Web Worker 预处理在 Worker 中解析 JSON/CSV 再传回主线程不阻塞 UI适用于超大数据文件(>10MB)
虚拟数据源实现 IDataSource 接口按需提供数据极致性能开发成本高,适合专业场景
压缩传输后端返回压缩数据(gzip/snappy)减少网络耗时需前后端配合
字段精简仅传输必要字段减小数据体积如剔除冗余描述字段

9.3 延迟更新与批量操作(suspend/resume)

方法名称语法用途代码示例注意事项
suspendPaintsheet.suspendPaint()暂停界面重绘sheet.suspendPaint();
// 批量设值/格式
sheet.resumePaint();
必须配对使用,否则界面不更新
resumePaintsheet.resumePaint()恢复重绘并刷新同上调用后立即重绘
suspendCalculationsheet.suspendCalculation()暂停公式重算sheet.suspendCalculation();
bulkUpdateData();
sheet.resumeCalculation();
避免每次修改都触发计算
resumeCalculationsheet.resumeCalculation()恢复计算并触发重算同上可能引起短暂卡顿
suspendEventssheet.suspendEvent(GC.Spread.Sheets.Events.ValueChanged)暂停特定事件触发sheet.suspendEvent("valueChanged");
updateManyCells();
sheet.resumeEvent("valueChanged");
防止事件风暴
resumeEventssheet.resumeEvent(event)恢复事件监听可指定单一事件

批量操作完整示例(性能提升可达 10x 以上):

sheet.suspendPaint();
sheet.suspendCalculation();
for (let i = 0; i < 5000; i++) {
    sheet.setValue(i, 0, data[i]);
}
sheet.resumeCalculation();
sheet.resumePaint();

9.4 内存管理与资源释放

方法/实践说明代码示例注意事项
销毁 Spread 实例释放所有资源spread.destroy();页面卸载前必须调用
解除事件绑定防止内存泄漏sheet.unbindAll();sheet.unbind("valueChanged", handler);特别是在动态创建/销毁场景
清理定时器若使用了 setInterval 等clearInterval(timerId);避免闭包持有对象引用
置空变量主动断开引用workbook = null; sheet = null;辅助垃圾回收
避免闭包泄漏不在事件中长期持有外部大对象尤其注意 this 的使用范围
图片资源管理及时清理不再使用的图片sheet.clearImages();图片过多易导致 OOM
使用弱引用(WeakMap/WeakSet)存储辅助数据const cache = new WeakMap();对象被回收时自动清理缓存

第十章:主题与样式定制

10.1 内置主题应用

方法/属性语法用途代码示例注意事项
setThemespread.setTheme(themeName)应用 SpreadJS 内置主题spread.setTheme(GC.Spread.Sheets.Theme.themes.dark);主题影响整个工作簿外观,支持 “default”, “lightGray”, “colorful” 等
Theme 枚举GC.Spread.Sheets.Theme.themes提供标准主题集合const theme = GC.Spread.Sheets.Theme.themes.colorful;
spread.setTheme(theme);
支持深色/浅色多种风格
单元格级主题样式style.backColor, foreColor结合主题设置单元格样式const style = new GC.Spread.Sheets.Style();
style.backColor = "#f0f8ff";
sheet.setStyle(0, 0, style);
可覆盖主题默认值
表格主题(Table Theme)table.tableStyle = TableThemes.xxx为表格区域应用独立样式table.tableStyle = GC.Spread.Sheets.Tables.TableThemes.medium6;不影响整体工作表主题
动态切换主题在运行时更改主题function switchToDark() {
spread.setTheme("dark");
saveUserPreference("theme", "dark");
}
建议保存用户偏好
自动适配系统主题检测 prefers-color-schemeif (window.matchMedia("(prefers-color-scheme: dark)").matches) {
spread.setTheme("dark");
}
提升用户体验一致性
主题重置恢复为默认主题spread.setTheme(null);清除自定义或内置主题

10.2 自定义 CSS 主题

方法/技术说明代码示例注意事项
覆盖 CSS 变量使用 CSS Custom Properties 修改主题颜色:root { --spread-primary-color: #1a73e8; --spread-header-bg: #e8f0fe; }需确保优先级高于默认样式
自定义 class 名为特定单元格添加 CSS 类sheet.getCell(0, 0).cssClass("highlight-cell");必须在 HTML 中定义对应样式
外部样式表引入创建 custom-theme.css 并引入.my-spread .highlight-cell { background: linear-gradient(to right, #ffecd2, #fcb69f); font-weight: bold; }推荐方式,便于维护
动态注入样式JavaScript 动态创建 <style> 标签const style = document.createElement('style');
style.textContent = '.custom-grid { border: 2px solid blue; }';
document.head.appendChild(style);
适用于运行时主题切换
字体定制通过 CSS 设置全局字体.gc-spread-sheet { font-family: "Segoe UI", sans-serif; }统一界面视觉风格
响应式主题根据屏幕尺寸调整样式@media (max-width: 768px) { .gc-spread-sheet { zoom: 0.8; } }移动端适配建议

10.3 样式优先级与覆盖规则

来源优先级顺序说明示例场景注意事项
默认样式1(最低)SpreadJS 内置基础样式所有未设置样式的单元格可被其他层级覆盖
主题样式2来自 setTheme() 的整体风格表头背景色、边框颜色影响大范围区域
行/列样式3通过 rows[i].style 或 columns[j].style 设置整行高亮、统一列宽字体比默认和主题优先
单元格样式4使用 getCell(row, col).foreColor(…) 等设置特定单元格红色文字最常用,局部控制
CSS 类(cssClass)5通过 .cssClass(“name”) 添加类名自定义高亮、动画效果若 CSS 权重高则优先
内联样式(!important)6(最高)在 CSS 中使用 !important 强制生效关键状态提示(如错误)尽量避免滥用
编辑状态伪类特殊如 :focus, :editing输入时背景变蓝浏览器原生行为

最佳实践: 明确层级关系,避免冲突。使用 BEM 命名约定(如 .spread-cell--error, .row-header-bold)。

提示: 可通过 Chrome DevTools 审查元素,查看最终计算样式(Computed Styles),确认优先级是否符合预期。


第十一章:插件与扩展开发

11.1 使用官方插件(如形状、图表)

插件类型引入方式初始化用途注意事项
图表(Charts)@grapecity/spread-sheets-chartssheet.charts.add(...)在表格中嵌入柱状图、折线图等需绑定数据范围,支持动态更新
形状(Shapes)@grapecity/spread-sheets-shapessheet.shapes.add(...)添加箭头、矩形、流程图元素可用于标注或可视化说明
PDF 导出@grapecity/spread-sheets-pdfGC.Spread.Sheets.PDF.export(...)将工作表导出为 PDF 文件支持分页、水印、加密
Excel IO@grapecity/spread-excelionew GC.Spread.Excel.IO()导入/导出 .xlsx 文件兼容 Office Excel 格式
表格(Tables)内置模块sheet.tables.add(...)创建结构化数据表,带筛选和格式支持主题样式和公式列
数据透视表@grapecity/spread-sheets-pivotsheet.pivotTables.add(...)实现多维数据分析类似 Excel PivotTable
条件格式内置功能sheet.conditionalFormats...根据规则自动着色(如大于平均值标红)支持数据条、色阶、图标集
公式函数扩展GC.Spread.CalcEngine.addFunction(...)注册自定义公式函数=MYFUNCTION(A1)可封装业务逻辑

使用步骤:

# 安装 NPM 包
npm install @grapecity/spread-sheets-charts
// 导入并注册
import * as Charts from '@grapecity/spread-sheets-charts';

// 调用 API
spread.registerPlugin(new Charts.Plugin());

11.2 开发自定义单元格类型

步骤方法/接口说明代码示例注意事项
创建类继承 BaseCellType实现自定义渲染与行为class StarRatingCell extends GC.Spread.Sheets.CellTypes.Base { ... }必须实现 paint 方法
渲染逻辑paint(context, value, x, y, width, height, style, cellStyle)绘制单元格内容使用 Canvas API 绘制图标、进度条等注意坐标系与缩放
编辑器集成createEditorElement, activateEditor, deactivateEditor支持编辑模式返回 input 元素并在激活时聚焦需处理 blur 事件提交值
值获取getEditorValue(editor)从编辑器提取数据return parseFloat(editor.value);确保类型正确
注册使用sheet.getCell(i, j).cellType(new StarRatingCell())应用到具体单元格const cell = sheet.getCell(1, 1);
cell.cellType(new StarRatingCell());
可批量设置
性能优化缓存绘制对象避免重复创建图形资源const cacheKey = ${value}-${w}x${h};大数据量时尤为重要

自定义单元格类型示例:

class StarRatingCell extends GC.Spread.Sheets.CellTypes.Base {
    paint(ctx, value, x, y, w, h, style, context) {
        drawStars(ctx, value, x, y, w, h);
    }
    getEditorValue(editor) {
        return editor.value;
    }
}

11.3 扩展工具栏与按钮

方法语法用途代码示例注意事项
DOM 操作添加按钮document.createElement()在工具栏容器中插入新按钮const btn = document.createElement("button");
btn.innerText = "导出JSON";
toolbar.appendChild(btn);
btn.onclick = () => exportToJson();
需定位正确的父容器
绑定事件addEventListener("click", handler)为自定义按钮绑定行为btn.addEventListener("click", () => {
const data = sheet.getData();
download(JSON.stringify(data), "data.json");
});
注意作用域与 this 指向
集成至 Ribbon(React/Vue)使用框架组件包装在 React 中将按钮放入 Toolbar 组件<Toolbar><Button onClick={save}>保存</Button></Toolbar>框架友好开发
状态同步监听 Spread 事件更新按钮状态sheet.bind("selectionchanged", updateCutButtonState);根据当前选区启用/禁用按钮
分组管理将相关按钮归入同一面板使用 <div class="btn-group"> 包裹提升 UI 组织性
响应式布局使用 Flex/CSS Grid 排列按钮.toolbar { display: flex; flex-wrap: wrap; }适应不同屏幕尺寸

11.4 封装可复用组件

组件类型封装方式优势实现要点注意事项
数据表格组件React/Vue/Angular 组件一次开发,多处复用接收 dataSource 属性,暴露 onSave, onValidate 回调,内部集成 CRUD 逻辑支持受控与非受控模式
模板工作簿预设格式的 .json 或 .xlsx 文件快速初始化相同结构表格保存配置模板:workbook.toJSON();加载时:workbook.fromJSON(template)可版本化管理
工具函数库JS 模块导出通用方法如 formatCurrency, validateEmail见下文示例提高代码复用率
Web Component使用原生 Custom Elements跨框架使用见下文示例真正意义上的组件化
npm 包发布打包为独立库团队共享或开源npm publish包含 TypeScript 类型声明更佳
配置中心JSON 配置驱动渲染动态生成不同表格{ columns: [{ name: "name", type: "text" }, ...] }实现低代码平台基础能力

工具函数库示例:

export function applyHeaderStyle(sheet) {
    const style = new GC.Spread.Sheets.Style();
    style.backColor = "#e0e0e0";
    sheet.getRange(0, 0, 1, 10).setStyle(style);
}

Web Component 示例:

class MySpreadGrid extends HTMLElement {
    connectedCallback() {
        initSpread(this.shadowRoot);
    }
}
customElements.define('my-spread-grid', MySpreadGrid);

第十二章:实战项目:构建一个类 Excel 数据录入系统

12.1 需求分析与界面设计

1. 核心需求

功能模块具体需求
数据录入支持用户在表格中直接输入文本、数字、日期等
单元格编辑控制某些列(如”编号”)只读,某些行(如合计行)禁止修改
多工作表管理包含”员工信息”、“薪资明细”、“统计报表”三个工作表
界面友好性支持冻结首行、高亮当前选区、自动列宽
工具栏操作提供”保存”、“导出为 Excel”、“打印”、“撤销/重做”按钮

2. 界面布局设计

<div class="app-container">
  <!-- 顶部工具栏 -->
  <div class="toolbar">
    <button id="saveBtn">保存</button>
    <button id="exportBtn">导出 Excel</button>
    <button id="printBtn">打印</button>
    <button id="undoBtn">撤销</button>
    <button id="redoBtn">重做</button>
  </div>

  <!-- SpreadJS 容器 -->
  <div id="ss" style="width: 100%; height: 600px; border: 1px solid #ccc;"></div>

  <!-- 状态栏 -->
  <div class="status-bar">
    当前工作表:<span id="currentSheet">员工信息</span> |
    选中区域:<span id="selectionRange">A1</span>
  </div>
</div>

3. 样式建议(CSS)

.app-container {
  font-family: "Segoe UI", sans-serif;
  max-width: 1400px;
  margin: 20px auto;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  border-radius: 8px;
  overflow: hidden;
}

.toolbar {
  background: #f5f5f5;
  padding: 10px;
  display: flex;
  gap: 8px;
  border-bottom: 1px solid #ddd;
}

.toolbar button {
  padding: 6px 12px;
  border: 1px solid #ccc;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}

.toolbar button:hover {
  background: #e9e9e9;
}

.status-bar {
  padding: 8px 12px;
  background: #f9f9f9;
  font-size: 13px;
  color: #555;
  border-top: 1px solid #eee;
}

12.2 数据模型设计与绑定

1. 数据结构设计

// 员工信息表数据模型
const employeeData = [
  { id: "E001", name: "张三", dept: "技术部", position: "前端工程师", hireDate: "2023-01-15", status: "在职" },
  { id: "E002", name: "李四", dept: "销售部", position: "客户经理", hireDate: "2022-08-10", status: "在职" },
  { id: "E003", name: "王五", dept: "人事部", position: "HR专员", hireDate: "2023-03-05", status: "试用" }
];

// 薪资明细表
const salaryData = [
  { id: "E001", month: "2024-01", base: 12000, bonus: 2000, total: 14000 },
  { id: "E002", month: "2024-01", base: 10000, bonus: 1500, total: 11500 },
  { id: "E001", month: "2024-02", base: 12000, bonus: 2500, total: 14500 }
];

2. 初始化 SpreadJS 与绑定数据

// 初始化 Spread 实例
const spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss"), {
  sheetCount: 3
});

const sheet1 = spread.getSheet(0);
sheet1.name("员工信息");
sheet1.setDataSource(employeeData);

const sheet2 = spread.getSheet(1);
sheet2.name("薪资明细");
sheet2.setDataSource(salaryData);

const sheet3 = spread.getSheet(2);
sheet3.name("统计报表");

3. 列绑定与标题设置

// 设置列绑定字段与显示名称
sheet1.bindColumn(0, { name: "id", displayName: "员工编号", readOnly: true });
sheet1.bindColumn(1, { name: "name", displayName: "姓名" });
sheet1.bindColumn(2, { name: "dept", displayName: "部门", editorType: "ComboBox", editorOptions: { items: ["技术部", "销售部", "人事部", "财务部"] } });
sheet1.bindColumn(3, { name: "position", displayName: "职位" });
sheet1.bindColumn(4, { name: "hireDate", displayName: "入职日期", formatter: "yyyy-MM-dd" });
sheet1.bindColumn(5, { name: "status", displayName: "状态", editorType: "ComboBox", editorOptions: { items: ["在职", "试用", "离职"] } });

// 自动列宽
sheet1.autoFitColumn(0, GC.Spread.Sheets.AutoFitType.cell);

4. 冻结窗格

// 冻结首行
sheet1.setFrozenCount(1, 0);

12.3 表单验证与错误提示

1. 单元格级验证

// 姓名不能为空
sheet1.addInvalidFormulaAlert({
  row: -1, col: 1, // 所有行的第2列(姓名)
  message: "姓名不能为空",
  alertStyle: GC.Spread.Sheets.Alerts.AlertStyle.error,
  showOnSelect: true
});

// 自定义验证规则
sheet1.setCellValidation(2, 2, new GC.Spread.Sheets.DataValidation.Rule({
  type: GC.Spread.Sheets.DataValidation.ValidationType.custom,
  formula1: '=LEN(TRIM(A2))>0', // 示例公式
  errorMessage: "部门不能为空",
  errorStyle: GC.Spread.Sheets.DataValidation.ErrorStyle.stop
}));

2. 编程方式验证(EditEnd 事件)

sheet1.bind(GC.Spread.Sheets.Events.EditEnd, (e, args) => {
  const { row, col, value } = args;
  let errorMsg = null;

  if (col === 1 && (!value || value.trim().length === 0)) {
    errorMsg = "【姓名】不能为空";
  }

  if (col === 4 && !isValidDate(value)) {
    errorMsg = "【入职日期】格式不正确";
  }

  if (errorMsg) {
    showErrorMessage(errorMsg);
    sheet1.getCell(row, col).backColor("#ffe6e6"); // 红色背景提示
    setTimeout(() => {
      sheet1.getCell(row, col).backColor(null); // 恢复
    }, 2000);
  }
});

function isValidDate(dateString) {
  const reg = /^\d{4}-\d{2}-\d{2}$/;
  if (!reg.test(dateString)) return false;
  const d = new Date(dateString);
  return d instanceof Date && !isNaN(d);
}

3. 错误提示 UI

function showErrorMessage(msg) {
  alert(`❌ 数据输入错误:${msg}`);
  // 或使用 Toast 组件:toast.error(msg);
}

12.4 导出功能实现

1. 导出为 Excel 文件

document.getElementById("exportBtn").addEventListener("click", () => {
  const excelIO = new GC.Spread.Excel.IO();
  const workbook = spread;

  workbook.save((data) => {
    // 使用 FileSaver.js 下载
    const blob = new Blob([data], { type: "application/octet-stream" });
    saveAs(blob, `员工数据_${new Date().toISOString().slice(0,10)}.xlsx`);
  }, (err) => {
    console.error("导出失败:", err);
    alert("导出失败,请重试");
  }, {
    fileType: GC.Spread.Excel.FileType.xlsx
  });
});

2. 导出为 JSON(用于保存或传输)

function exportToJson() {
  const currentSheet = spread.getActiveSheet();
  const data = currentSheet.getDataSource() || currentSheet.getData(0, 0, currentSheet.getRowCount(), currentSheet.getColumnCount());
  const jsonStr = JSON.stringify(data, null, 2);
  const blob = new Blob([jsonStr], { type: "application/json" });
  saveAs(blob, "data.json");
}

3. 打印功能

document.getElementById("printBtn").addEventListener("click", () => {
  spread.print({
    paperSize: GC.Spread.Sheets.Print.PaperKind.a4,
    printDirection: GC.Spread.Sheets.Print.PrintDirection.portrait,
    showBorder: true,
    showColor: true,
    showGridline: true,
    showHeader: true,
    showFooter: true
  });
});

12.5 多工作表协同与状态保存

1. 工作表切换监听

spread.bind(GC.Spread.Sheets.Events.ActiveSheetChanged, (e, args) => {
  const currentSheet = spread.getActiveSheet();
  document.getElementById("currentSheet").textContent = currentSheet.name();
});

2. 跨工作表数据联动

// 当"员工信息"表中新增员工时,自动在"薪资明细"中创建记录
sheet1.bind(GC.Spread.Sheets.Events.ValueChanged, (e, args) => {
  if (args.col === 0 && args.value && args.oldValue === null) { // 新增员工编号
    const salarySheet = spread.getSheet(1);
    const newRow = {
      id: args.value,
      month: new Date().getFullYear() + "-" + String(new Date().getMonth()+1).padStart(2, '0'),
      base: 8000,
      bonus: 0,
      total: 8000
    };
    const data = salarySheet.getDataSource();
    data.push(newRow);
    salarySheet.setDataSource(data);
  }
});

3. 状态保存(localStorage)

// 保存当前工作簿状态
function saveState() {
  const state = spread.toJSON();
  localStorage.setItem("spreadAppState", JSON.stringify(state));
  localStorage.setItem("activeSheetIndex", spread.getActiveSheetIndex());
  alert("✅ 当前状态已保存");
}

// 恢复状态
function loadState() {
  const saved = localStorage.getItem("spreadAppState");
  if (saved) {
    try {
      spread.fromJSON(JSON.parse(saved));
      const index = parseInt(localStorage.getItem("activeSheetIndex")) || 0;
      spread.setActiveSheetIndex(index);
      document.getElementById("currentSheet").textContent = spread.getActiveSheet().name();
    } catch (e) {
      console.error("恢复状态失败:", e);
    }
  }
}

// 绑定保存按钮
document.getElementById("saveBtn").addEventListener("click", saveState);

// 页面加载时恢复
window.addEventListener("load", loadState);

4. 撤销/重做支持

const undoManager = spread.undoManager();
undoManager.enabled(true);

document.getElementById("undoBtn").addEventListener("click", () => {
  if (undoManager.canUndo()) undoManager.undo();
});

document.getElementById("redoBtn").addEventListener("click", () => {
  if (undoManager.canRedo()) undoManager.redo();
});

项目总结

功能实现技术
数据录入setDataSource + bindColumn
验证提示addInvalidFormulaAlert + EditEnd 事件
导出 ExcelExcelIO.save()
多工作表协同getActiveSheet() + 跨 sheet 数据操作
状态持久化spread.toJSON() + localStorage
用户体验冻结窗格、工具栏、撤销重做

建议扩展功能:

  • 添加用户登录与权限控制
  • 接入后端 API 实现数据持久化
  • 增加数据透视表用于分析
  • 支持模板导入/导出