第一章: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.html、styles.css、script.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.Workbook | new 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 设置初始工作表数量 |
setActiveSheetIndex | workbook.setActiveSheetIndex(index) | 切换当前激活的工作表 | workbook.setActiveSheetIndex(0); | 索引从 0 开始,超出范围无效 |
getActiveSheet | workbook.getActiveSheet() | 获取当前活动工作表对象 | const sheet = workbook.getActiveSheet(); | 后续操作多基于 sheet 对象进行 |
第二章:基础工作表操作
2.1 工作簿与工作表的基本概念
| 概念名称 | 说明 | 注意事项 |
|---|
| 工作簿(Workbook) | 相当于一个 Excel 文件,包含一个或多个工作表。 | 一个页面可创建多个 Workbook 实例,但通常只用一个。 |
| 工作表(Worksheet) | 工作簿中的单个标签页,用于存储数据和样式。 | 每个工作表有独立的行列结构和数据区域。 |
| 活动工作表(Active Sheet) | 当前用户正在操作的工作表。 | 只能有一个活动工作表。 |
| Sheet 名称 | 每个工作表的唯一标识名称,默认为 “Sheet1”、“Sheet2” 等。 | 名称不能重复,长度限制 1-31 字符,不能含 \/*?[] 等特殊字符。 |
| Sheet 索引 | 工作表在工作簿中的位置索引,从 0 开始。 | 添加顺序决定索引位置。 |
2.2 添加、删除与切换工作表
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
addSheet | workbook.addSheet(name, sheet) | 添加一个新的工作表 | const newSheet = new GC.Spread.Sheets.Worksheet('MySheet');
workbook.addSheet(1, newSheet); | 第一个参数为插入位置索引;若省略 sheet 参数会自动创建新实例 |
removeSheet | workbook.removeSheet(name) | 删除指定名称的工作表 | workbook.removeSheet('MySheet'); | 不能删除最后一个工作表;删除后索引会重新排列 |
removeSheetAt | workbook.removeSheetAt(index) | 删除指定索引位置的工作表 | workbook.removeSheetAt(1); | 索引越界将抛出错误 |
setActiveSheetName | workbook.setActiveSheetName(name) | 切换当前活动工作表(通过名称) | workbook.setActiveSheetName('Sheet2'); | 名称不存在则无效 |
setActiveSheetIndex | workbook.setActiveSheetIndex(index) | 切换当前活动工作表(通过索引) | workbook.setActiveSheetIndex(0); | 索引越界无效 |
getSheetName | workbook.getSheetName(index) | 获取指定索引处的工作表名称 | const name = workbook.getSheetName(0); | 用于遍历所有工作表 |
getSheetFromName | workbook.getSheetFromName(name) | 根据名称获取工作表对象 | const sheet = workbook.getSheetFromName('Data'); | 名称不存在返回 null |
2.3 设置工作表名称与可见性
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setActiveSheetName | workbook.setActiveSheetName(newName) | 修改当前活动工作表的名称 | workbook.setActiveSheetName('Summary'); | 名称必须唯一,否则失败 |
getActiveSheetName | workbook.getActiveSheetName() | 获取当前活动工作表的名称 | const name = workbook.getActiveSheetName(); | 常用于状态显示 |
setSheetVisible | workbook.setSheetVisible(name, visible) | 设置指定工作表是否可见 | workbook.setSheetVisible('HiddenSheet', false); | 隐藏后仍可通过名称或索引访问数据 |
getSheetVisible | workbook.getSheetVisible(name) | 获取指定工作表的可见状态 | const isVisible = workbook.getSheetVisible('Sheet1'); | 返回布尔值 |
showSheetTabs | workbook.options.showSheetTabs = bool | 控制是否显示底部工作表标签栏 | workbook.options.showSheetTabs = false; | 默认为 true;设为 false 后用户无法手动切换 |
2.4 行列的基本操作(增删改查)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
addRows | sheet.addRows(index, count) | 在指定位置插入行 | sheet.addRows(5, 2); // 插入2行 | 插入后原有数据下移 |
addColumns | sheet.addColumns(index, count) | 在指定位置插入列 | sheet.addColumns(3, 1); // 插入1列 | 插入后原有数据右移 |
removeRows | sheet.removeRows(index, count) | 删除指定位置的若干行 | sheet.removeRows(5, 2); // 删除2行 | 删除后数据上移,不可撤销 |
removeColumns | sheet.removeColumns(index, count) | 删除指定位置的若干列 | sheet.removeColumns(3, 1); // 删除1列 | 删除后数据左移 |
getRowCount | sheet.getRowCount() | 获取当前行数 | const rows = sheet.getRowCount(); | 包括空行 |
getColumnCount | sheet.getColumnCount() | 获取当前列数 | const cols = sheet.getColumnCount(); | 包括空列 |
setRowCount | sheet.setRowCount(count) | 设置总行数(扩展或截断) | sheet.setRowCount(100); | 小于当前值会截断数据 |
setColumnCount | sheet.setColumnCount(count) | 设置总列数 | sheet.setColumnCount(20); | 小于当前值会丢失列数据 |
getRowHeight | sheet.getRowHeight(index) | 获取某行高度(像素) | const height = sheet.getRowHeight(0); | 默认 22px |
setRowHeight | sheet.setRowHeight(index, height) | 设置某行高度 | sheet.setRowHeight(0, 30); | 支持自定义高度 |
getColumnWidth | sheet.getColumnWidth(index) | 获取某列宽度 | const width = sheet.getColumnWidth(1); | 默认 74px |
setColumnWidth | sheet.setColumnWidth(index, width) | 设置某列宽度 | sheet.setColumnWidth(1, 100); | 支持调整列宽 |
2.5 单元格的基础赋值与读取
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setValue | sheet.setValue(row, col, value) | 设置指定单元格的值 | sheet.setValue(0, 0, 'Hello'); | 支持字符串、数字、布尔、日期等 |
getValue | sheet.getValue(row, col) | 获取指定单元格的值 | const val = sheet.getValue(0, 0); | 若为空返回 null |
setText | sheet.setText(row, col, text) | 设置单元格文本(保留格式) | sheet.setText(0, 1, '123'); | 即使是数字也作为文本处理 |
getText | sheet.getText(row, col) | 获取单元格显示文本 | const text = sheet.getText(0, 0); | 受格式影响,如日期显示为 “2025-01-01” |
setFormula | sheet.setFormula(row, col, formula) | 设置单元格公式 | sheet.setFormula(2, 0, '=A1+A2'); | 支持内置函数如 SUM、IF 等 |
getFormula | sheet.getFormula(row, col) | 获取单元格公式字符串 | const f = sheet.getFormula(2, 0); | 若无公式返回空字符串 |
clearCell | sheet.clearCell(row, col, clearOption) | 清除单元格内容或格式 | sheet.clearCell(0, 0, GC.Spread.Sheets.SheetArea.viewport); | clearOption 可选:all, content, format |
isDirty | sheet.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 字体、颜色与对齐方式设置
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
font | sheet.getCell(row, col).font(fontString) | 设置单元格字体样式 | sheet.getCell(0, 0).font("bold 14px Arial"); | 支持 CSS 字体语法:[bold/italic] size family |
foreColor | sheet.getCell(row, col).foreColor(color) | 设置字体颜色 | sheet.getCell(0, 0).foreColor("red");
// 或 "#FF0000" | 接受颜色名称或十六进制值 |
backColor | sheet.getCell(row, col).backColor(color) | 设置单元格背景色 | sheet.getCell(0, 0).backColor("#FFFFE0"); | 优先级低于条件格式和填充样式 |
hAlign | sheet.getCell(row, col).hAlign(hAlignment) | 设置水平对齐方式 | sheet.getCell(0, 0).hAlign(GC.Spread.Sheets.HorizontalAlign.center); | 可选值:left, center, right, general, justify |
vAlign | sheet.getCell(row, col).vAlign(vAlignment) | 设置垂直对齐方式 | sheet.getCell(0, 0).vAlign(GC.Spread.Sheets.VerticalAlign.middle); | 可选值:top, middle, bottom, baseline |
wordWrap | sheet.getCell(row, col).wordWrap(wrap) | 设置自动换行 | sheet.getCell(0, 0).wordWrap(true); | 需配合足够行高显示完整内容 |
textIndent | sheet.getCell(row, col).textIndent(indent) | 设置文本缩进(空格数) | sheet.getCell(0, 0).textIndent(2); | 仅影响显示,不改变实际值 |
3.3 边框样式配置
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
borders.getBorder | sheet.borders.getBorder(row, col, borderType) | 获取指定边框对象 | const border = sheet.borders.getBorder(0, 0, GC.Spread.Sheets.BorderType.edgeBottom); | 用于读取当前边框样式 |
borders.setBorder | sheet.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 等 |
LineBorder | new 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 等 |
removeBorder | sheet.borders.removeBorder(row, col, borderType) | 移除指定边框 | sheet.borders.removeBorder(0, 0, GC.Spread.Sheets.BorderType.all); | 清除后恢复为无边框状态 |
setRangeBorder | sheet.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 背景填充与条件样式
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
background | sheet.getCell(row, col).background(color) | 设置单元格背景色 | sheet.getCell(0, 0).background("#E6E6FA"); | 与 backColor 功能相同 |
style.backColor | sheet.setStyle(row, col, style) 结合 style.backColor | 批量设置样式对象 | const style = new GC.Spread.Sheets.Style();
style.backColor = "#D3D3D3";
sheet.setStyle(0, 0, style); | 适用于复杂样式组合 |
conditionalFormats.addCondition | sheet.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); | 适用于数值列对比 |
ClearCondition | sheet.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 数字格式与日期格式化
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
formatter | sheet.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元"); | 支持文本拼接 |
getFormatter | sheet.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 自定义公式注册与使用
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
addFunction | GC.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 重算 |
| 自动重算 | 默认开启,数据变化时自动更新公式结果 | 无需手动干预 | 性能敏感场景可关闭 |
setCalcMode | workbook.options.calcMode = mode | 设置计算模式 | workbook.options.calcMode = GC.Spread.Sheets.CalcMode.manual; |
recalc | workbook.recalc() | 手动触发全部重算 | workbook.recalc(); |
recalcCell | sheet.recalcCell(row, col) | 重算指定单元格 | sheet.recalcCell(2, 0); |
getFormula | sheet.getFormula(row, col) | 获取公式字符串 | const f = sheet.getFormula(2, 0); |
getDependents | sheet.getDependents(row, col) | 获取依赖该单元格的所有单元格 | const deps = sheet.getDependents(0, 0); |
getSiblings | sheet.getSiblings(row, col) | 获取引用该单元格的公式单元格 | 同 getDependents |
4.4 错误处理与调试技巧
| 方法/技巧 | 说明 | 代码示例 | 注意事项 |
|---|
| 常见错误类型 | #VALUE! (类型错误), #DIV/0! (除零), #REF! (引用无效), #NAME? (函数名错误) | =1/0 → #DIV/0! | 观察错误提示定位问题 |
isErrorValue | GC.Spread.CalcEngine.isErrorValue(value) | 判断值是否为错误类型 | if (GC.Spread.CalcEngine.isErrorValue(val)) { ... } |
| try-catch 包装 | 在自定义函数中使用 try-catch | try { return x / y; } catch(e) { return "#ERROR!"; } | 防止崩溃,返回友好提示 |
| getValue + 检查 | 读取前判断是否为错误 | const v = sheet.getValue(0, 0);
if (v === null ...) | |
| 公式追踪 | 使用开发者工具查看公式依赖 | 无直接 API,需手动调试 | 可打印 getFormula 和 getDependents |
suspendCalculation | sheet.suspendCalculation() | 暂停计算(批量操作优化) | 见下文示例 |
resumeCalculation | sheet.resumeCalculation() | 恢复计算并触发重算 | 同上 |
| 日志输出 | 使用 console.log 输出中间值 | console.log("A1=", sheet.getValue(0,0)); | 调试自定义函数时非常有用 |
批量操作优化示例:
sheet.suspendCalculation();
// 批量设值
sheet.resumeCalculation();
第五章:数据绑定与数据源集成
5.1 静态数据绑定(数组、对象)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setArray | sheet.setArray(row, col, dataArray) | 将二维数组数据批量写入工作表 | const data = [["姓名", "年龄"], ["张三", 25], ["李四", 30]];
sheet.setArray(0, 0, data); | dataArray 应为嵌套数组结构 |
getArray | sheet.getArray(row, col, rowCount, colCount) | 从指定区域读取数据为二维数组 | const arr = sheet.getArray(0, 0, 3, 2); | 返回包含 null 的矩形数据块 |
setDataSource | sheet.setDataSource(source) | 绑定对象数组作为数据源 | const users = [{name: "A", age: 20}, {name: "B", age: 22}];
sheet.setDataSource(users); | 自动映射属性到列 |
getData | sheet.getData() | 获取当前数据源(若已绑定) | const src = sheet.getData(); | 仅当使用 setDataSource 时有效 |
autoGenerateColumns | sheet.options.autoGenerateColumns = bool | 是否根据数据源自动创建列头 | sheet.options.autoGenerateColumns = true; | 默认为 true;设为 false 可手动定义列 |
setColumnWidth | sheet.setColumnWidth(col, width) | 配合数据绑定调整列宽 | sheet.setColumnWidth(0, 100); | 建议在绑定后设置以优化显示 |
5.2 动态数据绑定(Ajax、Promise)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| fetch + then | fetch(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.get | axios.get(url).then(...) | 使用 Axios 库获取数据 | axios.get('/api/report').then(response => { sheet.setDataSource(response.data); }); | 支持 Promise,语法简洁 |
| async/await | const data = await fetch(...).then(...) | 使用 async 函数简化异步逻辑 | 见下文示例 | 更清晰的控制流 |
| setDataEmpty | sheet.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 对象管理复杂数据
| 类型/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| BindingSource | new 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 }); | 适用于审计日志或联动更新 |
| getCurrent | bs.getCurrent() | 获取当前选中行对应的数据对象 | const current = bs.getCurrent(); | 需配合选择模式使用 |
| addNew / endEdit | bs.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 启用/禁用单元格编辑
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
isEditable | sheet.getCell(row, col).isEditable(bool) | 设置指定单元格是否可编辑 | sheet.getCell(0, 0).isEditable(false); | 优先级高于行/列设置 |
options.isProtected | sheet.options.isProtected = bool | 启用工作表保护(禁用编辑) | sheet.options.isProtected = true; | 启用后所有单元格默认不可编辑 |
defaultStyle | sheet.setDefaultStyle(style) | 设置默认样式中的可编辑性 | 见下文示例 | 影响未单独设置的单元格 |
setColumnReadOnly | sheet.columns[col].locked = bool | 批量设置某列是否只读 | sheet.columns[0].locked = true; | 需配合 isProtected=true 生效 |
setRowReadOnly | sheet.rows[row].locked = bool | 批量设置某行是否只读 | sheet.rows[0].locked = true; | 锁定后用户无法修改该行内容 |
canUserEditFormula | sheet.options.canUserEditFormula = bool | 控制是否允许用户编辑公式 | sheet.options.canUserEditFormula = false; | 防止误改关键计算逻辑 |
canUserDragFill | sheet.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 选择模式与范围控制
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
selectionMode | sheet.options.selectionMode = mode | 设置选择模式 | sheet.options.selectionMode = GC.Spread.Sheets.SelectionMode.row; | 可选:none, cell, row, column, range |
selectionUnit | sheet.options.selectionUnit = unit | 设置选择单位 | sheet.options.selectionUnit = GC.Spread.Sheets.SelectionUnit.range; | cell 或 range |
setSelection | sheet.setSelection(row, col, rowCount, colCount) | 编程方式设置选区 | sheet.setSelection(1, 1, 3, 2); | 会触发 SelectionChanged 事件 |
getSelections | sheet.getSelections() | 获取当前所有选区 | const ranges = sheet.getSelections(); | 返回 CellRange 数组 |
clearSelection | sheet.clearSelection() | 清除当前选择 | sheet.clearSelection(); | 无参数,清除所有高亮 |
allowUserZoom | sheet.options.allowUserZoom = bool | 是否允许用户缩放视图 | sheet.options.allowUserZoom = true; | 默认开启,影响选择区域显示 |
showResizeIndicator | sheet.options.showResizeIndicator = bool | 显示行列调整指示器 | sheet.options.showResizeIndicator = true; | 提升用户体验 |
6.3 剪切、复制、粘贴行为配置
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
canUserCopy | sheet.options.canUserCopy = bool | 是否允许复制 | sheet.options.canUserCopy = false; | 禁用 Ctrl+C 和右键复制 |
canUserCut | sheet.options.canUserCut = bool | 是否允许剪切 | sheet.options.canUserCut = false; | 禁用 Ctrl+X |
canUserPaste | sheet.options.canUserPaste = bool | 是否允许粘贴 | sheet.options.canUserPaste = false; | 禁用 Ctrl+V |
pasteSpecial | sheet.paste(row, col, data, mode) | 控制粘贴内容类型 | sheet.paste(0, 0, clipboardData, GC.Spread.Sheets.ClipboardPasteOptions.values); | 可选:all, values, formats, formulas |
getClipValue | sheet.getClipValue() | 获取将要粘贴的数据 | const data = sheet.getClipValue(); | 用于拦截或修改粘贴内容 |
setClipValue | sheet.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 拖拽与填充句柄控制
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
canUserDragDrop | sheet.options.canUserDragDrop = bool | 是否允许单元格拖拽移动 | sheet.options.canUserDragDrop = false; | 默认为 true |
canUserDragFill | sheet.options.canUserDragFill = bool | 是否允许拖动填充句柄(+号) | sheet.options.canUserDragFill = false; | 关闭后无法快速填充序列 |
autoFillType | sheet.autoFill.type = type | 设置自动填充类型 | sheet.autoFill.type = GC.Spread.Sheets.AutoFillType.copy; | 可选:copy, series, format, values |
autoFillDirection | sheet.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) | 方向键 | 默认支持单元格间移动 | — | 内置行为 |
enterKeyAction | sheet.options.enterKeyAction = action | 设置回车键移动方向 | sheet.options.enterKeyAction = GC.Spread.Sheets.EnterKeyAction.moveDown; | 可选:moveNone, moveUp, moveDown, moveLeft, moveRight, moveNext, movePrevious |
tabKeyAction | sheet.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) | 监听按键释放 | — | 辅助组合键判断 |
stopDefaultKeyBinding | spread.commandManager().register() | 注册自定义命令替换默认快捷键 | GC.Spread.Commands.register("mySave", mySaveFunc);
spread.commandManager().register("ctrl+s", "mySave"); | 高级用法,覆盖 Ctrl+S |
isEnterInEditMode | sheet.options.isEnterInEditMode = bool | 回车是否进入编辑模式 | sheet.options.isEnterInEditMode = true; | 类似 Excel 双击效果 |
scrollWithArrowKeys | sheet.options.scrollWithArrowKeys = bool | 方向键是否触发滚动 | sheet.options.scrollWithArrowKeys = true; | 大表格中建议开启 |
第七章:高级功能模块
7.1 冻结窗格与分页设置
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setFrozenCount | sheet.setFrozenCount(row, col) | 设置冻结行列数 | sheet.setFrozenCount(1, 1); // 冻结首行首列 | 冻结线前的内容固定不动 |
getFrozenCount | sheet.getFrozenCount() | 获取当前冻结数量 | const frozen = sheet.getFrozenCount(); | 返回 {row: n, col: m} |
frozenFromRow/frozenFromCol | sheet.frozenFromRow = 2; sheet.frozenFromCol = 1; | 冻结指定位置为起点 | 更灵活的冻结控制 | |
options.viewportMargins | sheet.options.viewportMargins | 设置视口边距(分页打印用) | sheet.options.viewportMargins = { top: 50, bottom: 50 }; | 控制打印时每页内容高度 |
pageSetup | sheet.pageSetup | 配置打印页面参数 | sheet.pageSetup.orientation = GC.Spread.Sheets.Print.Orientation.landscape; | 设置纸张方向、边距等 |
printTitleRows | sheet.printTitleRows(start, end) | 设置每页重复打印的标题行 | sheet.printTitleRows(0, 0); // 每页打第一行 | 适用于多页报表 |
printTitleColumns | sheet.printTitleColumns(start, end) | 设置每页重复打印的标题列 | sheet.printTitleColumns(0, 0); | 常用于宽表格 |
pageCount | sheet.getPageCount() | 获取分页总数(打印预览) | const pages = sheet.getPageCount(); | 需先设置 pageSetup |
7.2 合并单元格与跨列显示
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
addSpan | sheet.addSpan(row, col, rowCount, colCount) | 合并指定区域单元格 | sheet.addSpan(0, 0, 2, 3); // 2x3 区域 | 只保留左上角单元格的值和样式 |
removeSpan | sheet.removeSpan(row, col) | 取消合并(需指定左上角) | sheet.removeSpan(0, 0); | 拆分后其他格为空 |
isSpanned | sheet.isSpanned(row, col) | 判断某单元格是否属于合并区域 | if (sheet.isSpanned(1, 1)) { ... } | 返回 boolean |
getSpan | sheet.getSpan(row, col) | 获取该位置所属合并区域范围 | const range = sheet.getSpan(0, 0); | 返回 CellRange 对象 |
mergeCells | sheet.mergeCells(row, col, rowCount, colCount) | 同 addSpan,语义更明确 | sheet.mergeCells(1, 1, 1, 4); | 功能完全相同 |
unmergeCells | sheet.unmergeCells(row, col) | 同 removeSpan | sheet.unmergeCells(0, 0); | 语法糖 |
options.allowCellOverflow | sheet.options.allowCellOverflow = bool | 是否允许内容溢出到邻近空单元格 | sheet.options.allowCellOverflow = true; | 类似 Excel 的”合并居中”显示效果 |
7.3 表格(Table)对象的创建与样式
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
addTable | sheet.tables.add(name, row, col, rowCount, colCount, tableStyle) | 创建表格区域 | sheet.tables.add("MyTable", 0, 0, 10, 5, GC.Spread.Sheets.Tables.TableThemes.medium9); | 自动启用筛选和样式 |
getTable | sheet.tables.getTable(name) | 获取表格对象 | const table = sheet.tables.getTable("MyTable"); | 用于后续操作 |
bindColumn | table.bindColumn("field", bindingInfo) | 绑定字段与数据源属性 | table.bindColumn("name", { displayName: "姓名", formatter: "0.00" }); | 支持格式化和显示名 |
showHeader | table.showHeader = bool | 是否显示表头 | table.showHeader = true; | 默认开启 |
showFilter | table.showFilter = bool | 是否显示筛选下拉 | table.showFilter = false; | 控制用户筛选权限 |
resize | table.resize(newRowCount, newColCount) | 调整表格大小 | table.resize(20, 6); | 自动扩展数据区域 |
deleteTable | sheet.tables.remove(name) | 删除表格(保留数据) | sheet.tables.remove("MyTable"); | 数据仍保留在工作表中 |
tableStyle | table.tableStyle = styleName | 更换表格主题样式 | table.tableStyle = GC.Spread.Sheets.Tables.TableThemes.light1; | 支持多种内置主题 |
7.4 分组与折叠(Grouping)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
groupRows | sheet.groupRows(start, end) | 对指定行范围进行分组 | sheet.groupRows(2, 5); // 折叠第3~6行 | 生成可折叠层级 |
groupColumns | sheet.groupColumns(start, end) | 对指定列范围进行分组 | sheet.groupColumns(1, 3); // 折叠B:D列 | 支持水平分组 |
ungroupRows | sheet.ungroupRows(start, end) | 取消行分组 | sheet.ungroupRows(2, 5); | 恢复展开状态 |
ungroupColumns | sheet.ungroupColumns(start, end) | 取消列分组 | sheet.ungroupColumns(1, 3); | — |
collapseGroup | sheet.collapseGroup(start, end, isRow) | 折叠指定分组 | sheet.collapseGroup(2, 5, true); | 手动控制展开/折叠 |
expandGroup | sheet.expandGroup(start, end, isRow) | 展开指定分组 | sheet.expandGroup(2, 5, true); | — |
isRowCollapsed | sheet.isRowCollapsed(index) | 判断某行是否被折叠 | if (sheet.isRowCollapsed(3)) { ... } | 用于状态判断 |
options.showGroupOutline | sheet.options.showGroupOutline = bool | 是否显示分组轮廓线 | sheet.options.showGroupOutline = true; | 默认显示,可关闭 |
7.5 打印与导出 PDF/Excel
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
print | spread.print() | 调用浏览器打印对话框 | spread.print({ paperSize: GC.Spread.Sheets.Print.PaperKind.a4, printDirection: GC.Spread.Sheets.Print.PrintDirection.landscape }); | 支持打印设置 |
exportToPdf | GC.Spread.Sheets.PDF.export(spread, stream, options) | 导出为 PDF | GC.Spread.Sheets.PDF.export(spread, stream, { scaleMode: GC.Spread.Sheets.Print.ScaleMode.fitWidth }); | 需引入 @grapecity/spread-sheets-pdf 包 |
exportToImage | GC.Spread.Sheets.Image.export(spread, options) | 导出为图片(PNG/JPG) | — | 适用于快照分享 |
saveWorkbook | workbook.save(fileName, options) | 保存为 Excel 文件 | workbook.save("report.xlsx", { fileType: GC.Spread.Excel.FileType.xlsx }); | 需引入 @grapecity/spread-excelio |
openWorkbook | workbook.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 等)
| 事件名称 | 语法 | 触发时机 | 代码示例 | 注意事项 |
|---|
| ValueChanged | sheet.bind(GC.Spread.Sheets.Events.ValueChanged, handler) | 单元格值发生改变时 | sheet.bind("valueChanged", (e, args) => { console.log(值从 ${args.oldValue} 变为 ${args.value}); }); | 包括公式重算导致的值变 |
| CellValueChanged | sheet.bind("cellvaluechanged", handler) | 特定单元格内容被用户修改 | sheet.bind("cellvaluechanged", (e, args) => { if (args.row === 0 && args.col === 0) { /* 处理 */ } }); | 仅响应直接输入,不包含公式 |
| EditStart | sheet.bind("editstart", handler) | 编辑模式开始(进入单元格编辑) | sheet.bind("editstart", (e, args) => { highlightRelatedCells(args.row, args.col); }); | 可用于高亮关联区域 |
| EditEnd | sheet.bind("editend", handler) | 编辑完成并提交值后 | sheet.bind("editend", (e, args) => { if (args.isCancelled) return; validateInput(args.row, args.col, args.value); }); | isCancelled 判断是否按 Esc 取消 |
| SelectionChanged | sheet.bind("selectionchanged", handler) | 选区发生变化时 | sheet.bind("selectionchanged", (e, args) => { const range = args.newSelections[0]; updateStatusBar(range); }); | newSelections 为 CellRange 数组 |
| RowHeightChanged | sheet.bind("rowheightchanged", handler) | 行高调整后 | sheet.bind("rowheightchanged", (e, args) => { saveLayout(); }); | 适用于保存自定义布局 |
| ColumnWidthChanged | sheet.bind("columnwidthchanged", handler) | 列宽调整后 | sheet.bind("columnwidthchanged", (e, args) => { console.log("列", args.col, "宽度:", args.newSize); }); | 可用于响应式设计同步 |
| SheetNameChanged | workbook.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 | 建议关键逻辑后注册 |
unbind | sheet.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.setMenuItems | contextMenu.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 单元格双击/单击行为扩展
| 事件 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| CellClick | sheet.bind("cellclick", handler) | 监听单元格单击 | sheet.bind("cellclick", (e, args) => { if (args.button === 0) { navigateToDetail(args.row); } }); | button: 0=左键, 2=右键 |
| CellDoubleClick | sheet.bind("celldoubleclick", handler) | 监听单元格双击 | sheet.bind("celldoubleclick", (e, args) => { startInlineEditor(args.row, args.col); }); | 常用于触发编辑或弹窗 |
isDoubleClickEdit | sheet.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 默认启用虚拟滚动 |
viewportRows | sheet.options.viewportRows | 设置视口中显示的行数 | sheet.options.viewportRows = 20; | 影响滚动性能与内存占用 |
viewportColumns | sheet.options.viewportColumns | 设置视口中显示的列数 | sheet.options.viewportColumns = 15; | 宽表格需合理设置 |
| setRowHeight / setColWidth | sheet.setRowHeight(row, height) | 避免频繁动态调整行列高宽 | for (let i = 0; i < 10000; i++) { sheet.setRowHeight(i, 25); } | 动态变化会降低渲染性能 |
| disable animations | sheet.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)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
suspendPaint | sheet.suspendPaint() | 暂停界面重绘 | sheet.suspendPaint();
// 批量设值/格式
sheet.resumePaint(); | 必须配对使用,否则界面不更新 |
resumePaint | sheet.resumePaint() | 恢复重绘并刷新 | 同上 | 调用后立即重绘 |
suspendCalculation | sheet.suspendCalculation() | 暂停公式重算 | sheet.suspendCalculation();
bulkUpdateData();
sheet.resumeCalculation(); | 避免每次修改都触发计算 |
resumeCalculation | sheet.resumeCalculation() | 恢复计算并触发重算 | 同上 | 可能引起短暂卡顿 |
suspendEvents | sheet.suspendEvent(GC.Spread.Sheets.Events.ValueChanged) | 暂停特定事件触发 | sheet.suspendEvent("valueChanged");
updateManyCells();
sheet.resumeEvent("valueChanged"); | 防止事件风暴 |
resumeEvents | sheet.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 内置主题应用
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setTheme | spread.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-scheme | if (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-charts | sheet.charts.add(...) | 在表格中嵌入柱状图、折线图等 | 需绑定数据范围,支持动态更新 |
| 形状(Shapes) | @grapecity/spread-sheets-shapes | sheet.shapes.add(...) | 添加箭头、矩形、流程图元素 | 可用于标注或可视化说明 |
| PDF 导出 | @grapecity/spread-sheets-pdf | GC.Spread.Sheets.PDF.export(...) | 将工作表导出为 PDF 文件 | 支持分页、水印、加密 |
| Excel IO | @grapecity/spread-excelio | new GC.Spread.Excel.IO() | 导入/导出 .xlsx 文件 | 兼容 Office Excel 格式 |
| 表格(Tables) | 内置模块 | sheet.tables.add(...) | 创建结构化数据表,带筛选和格式 | 支持主题样式和公式列 |
| 数据透视表 | @grapecity/spread-sheets-pivot | sheet.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 事件 |
| 导出 Excel | ExcelIO.save() |
| 多工作表协同 | getActiveSheet() + 跨 sheet 数据操作 |
| 状态持久化 | spread.toJSON() + localStorage |
| 用户体验 | 冻结窗格、工具栏、撤销重做 |
建议扩展功能:
- 添加用户登录与权限控制
- 接入后端 API 实现数据持久化
- 增加数据透视表用于分析
- 支持模板导入/导出