脏数据管理

SpreadJS 提供了脏数据管理功能,可以追踪用户对表格的编辑操作,包括修改单元格、插入行和删除行等变更记录。通过脏数据 API,你可以轻松获取变更的内容,无需遍历整个工作表,特别适用于数据提交、变更对比和协同编辑等场景。

概述 本 Demo 展示了如何使用 SpreadJS 的脏数据管理功能,追踪和获取用户对表格的各种编辑操作。Demo 演示了获取脏单元格、脏行、插入行、删除行,以及清除待处理更改等功能。 实现思路 初始化表格并填充基础数据,然后调用 clearPendingChanges() 清除初始化时的脏状态 绑定 CellChanged 事件,实时监听单元格修改并显示变更信息 绑定 RowChanged 事件,监听行的插入和删除操作 提供多个按钮获取不同类型的脏数据:脏单元格、脏行、插入行、删除行 实现清除功能,支持全表清除或按指定范围清除 提供设置行数/列数的按钮,演示某些操作会自动清除脏状态 代码解析 监听单元格变更事件 这段代码监听单元格变更事件,当单元格被修改时,首先使用 hasPendingChanges(row, col) 检查该位置是否有待处理的更改,然后调用 getDirtyCells(row, col) 获取脏单元格信息,包括行号、列号、旧值和新值。 监听行变更事件 这段代码监听行变更事件,propertyName 可以是 addRows(插入行)或 deleteRows(删除行),通过事件参数获取变更的行索引和数量。 获取所有脏单元格 调用 getDirtyCells() 不传参数时,返回整个工作表中所有脏单元格的数组。每个脏单元格对象包含 row(行索引)、col(列索引)、oldValue(旧值)和 newValue(新值)属性。 获取选中区域的脏单元格 调用 getDirtyCells(row, col, rowCount, colCount) 可以获取指定范围内的脏单元格。参数包括起始行、起始列、行数和列数。 清除待处理更改(按范围) clearPendingChanges() 方法支持传入一个可选参数对象,通过 clearType 指定要清除的类型:1 表示脏数据,2 表示插入,4 表示删除。可以使用位运算组合多个类型。还可以指定 row、col、rowCount、colCount 来清除特定范围的待处理更改。 运行效果 编辑任意单元格,会在文本框中显示变更信息,例如:"单元格 (1, 2) 从 A2 更改为 新内容" 点击"插入行"或"删除行"按钮,会在选中位置执行操作并显示提示 点击"获取脏行"按钮,显示所有包含脏单元格的行索引 点击"获取所有脏单元格"按钮,显示所有被修改的单元格信息(包括旧值和新值) 点击"获取选中区域的脏单元格"按钮,只显示当前选中范围内的脏单元格 点击"获取插入的行"或"获取删除的行"按钮,显示相应操作的行索引 勾选"按范围清除"和类型选项,然后点击"清除待处理更改",只会清除选中区域内的指定类型更改 点击"设置行数"或"设置列数"按钮,会自动清除所有脏状态(因为这些操作会重新设置工作表结构) 脏数据定义 SpreadJS 中的脏数据遵循以下规则: 单元格值变更:只有单元格值的变更才会导致其成为脏数据 脏单元格所在的行:如果单元格是脏数据,其所在的行也是脏行 插入行的特殊性:如果插入了一行并为其设置值,此行是插入行而非脏行,被设置的单元格也不是脏单元格 数据绑定场景:加载绑定数据时不是脏数据,但修改绑定数据后会变为脏数据 撤销操作:脏数据的状态不会因为撤销操作而改变 API 参考 getDirtyCells 方法 row:可选,起始行索引 col:可选,起始列索引 rowCount:可选,行数 colCount:可选,列数 返回值:脏单元格数组,每个对象包含 row、col、oldValue、newValue getDirtyRows 方法 返回值:脏行数组,如果存在绑定数据则包含 row、item、originalItem,否则只包含 row getInsertRows 方法 返回值:插入行数组,每个对象包含 row 和 item 信息 getDeletedRows 方法 返回值:删除行数组,如果存在绑定数据则包含 row 和 originalItem,否则只包含 row hasPendingChanges 方法 返回值:布尔值,表示是否存在脏数据、插入或删除状态 clearPendingChanges 方法 clearChangeInfo.row:可选,清除区域的行索引 clearChangeInfo.col:可选,清除区域的列索引 clearChangeInfo.rowCount:可选,清除区域的行数 clearChangeInfo.colCount:可选,清除区域的列数 clearChangeInfo.clearType:可选,清除类型,值为 GC.Spread.Sheets.ClearPendingChangeType 枚举(1-脏数据,2-插入,4-删除),默认为脏数据 自动清除脏状态的操作 调用以下方法会自动清除所有脏状态: setRowCount() - 设置行数 setColumnCount() - 设置列数 fromJSON() - 加载 JSON 数据 setDataSource() - 设置数据源
var spreadNS = GC.Spread.Sheets; window.onload = function () { var spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss"), { sheetCount: 1 }); initSpread(spread); }; function initSpread(spread) { var sheet = spread.getSheet(0); sheet.suspendPaint(); for (var r = 0; r < 30; r++) { sheet.setText(r, 0, "A" + (r + 1)); } for (var c = 1; c < 20; c++) { sheet.setText(0, c, String.fromCharCode(65 + c) + "1"); } sheet.resumePaint(); sheet.clearPendingChanges(); var _getElementByIdtaResult = _getElementById("taResult"); function getChangedCellData(dirtyItem) { return ["单元格 (", dirtyItem.row, ",", dirtyItem.col, ") 从 ", dirtyItem.oldValue, " 更改为 ", dirtyItem.newValue].join(""); } function getDirtyCellData(dirtyItem) { return ["单元格 (", dirtyItem.row, ",", dirtyItem.col, ") 旧值: ", dirtyItem.oldValue, " 新值: ", dirtyItem.newValue].join(""); } function appendResult(txt) { _getElementByIdtaResult.value=_getElementByIdtaResult.value + txt + "\n"; } spread.bind(spreadNS.Events.CellChanged, function (event, data) { var row = data.row, col = data.col; if (row === undefined || col === undefined) { return; } if (sheet.hasPendingChanges(row, col)) { var dirtyDataArray = sheet.getDirtyCells(row, col); if (dirtyDataArray.length > 0) { appendResult(getChangedCellData(dirtyDataArray[0])); } } }); spread.bind(spreadNS.Events.RowChanged, function (event, data) { var row = data.row, count = data.count, propName = data.propertyName; if (row === undefined || count === undefined || propName === undefined) { return; } if (propName === "addRows" || propName === "deleteRows") { appendResult(propName + " @ " + row + (count > 1 ? " count: " + count : "")); } }); _getElementById("btnInsertRow").addEventListener('click',function () { var sels = sheet.getSelections(); var len = sels.length; if (len > 0) { var s = sels[0]; sheet.addRows(s.row, 1); } }); _getElementById("btnDeleteRow").addEventListener('click',function () { var sels = sheet.getSelections(); var len = sels.length; if (len > 0) { var s = sels[0]; sheet.deleteRows(s.row, s.rowCount); } }); _getElementById("btnGetSelectionDirtyCells").addEventListener('click',function () { var sels = sheet.getSelections(); var len = sels.length; if (len > 0) { var s = sels[0]; var row = s.row, col = s.col; if (row < 0) { row = 0; } if (col < 0) { col = 0; } var cells = sheet.getDirtyCells(row, col, s.rowCount, s.colCount); if (cells.length > 0) { appendResult("选中区域的脏单元格:\n" + cells.map(function (item) { return getDirtyCellData(item); }).join("\n")); } } }); _getElementById("btnGetDirtyCells").addEventListener('click',function () { var cells = sheet.getDirtyCells(); if (cells.length > 0) { appendResult("所有脏单元格:\n" + cells.map(function (item) { return getDirtyCellData(item); }).join("\n")); } }); _getElementById("btnGetDirtyRows").addEventListener('click',function () { var rows = sheet.getDirtyRows(); if (rows.length > 0) { appendResult("脏行 @ " + rows.map(function (item) { return item.row; }).join(", ")); } }); _getElementById("btnGetInsertRows").addEventListener('click',function () { var rows = sheet.getInsertRows(); if (rows.length > 0) { appendResult("已插入的行 @ " + rows.map(function (item) { return item.row; }).join(", ")); } }); _getElementById("btnGetDeleteRows").addEventListener('click',function () { var rows = sheet.getDeletedRows(); if (rows.length > 0) { appendResult("已删除的行 @ " + rows.map(function (item) { return item.row; }).join(", ")); } }); _getElementById("btnClearPendingChanges").addEventListener('click',function () { var clearByRange = _getElementById("clearByRange").checked; if (clearByRange) { var dirty = _getElementById("clearDirty").checked ? 1 : 0; var insert = _getElementById("clearInsert").checked ? 2 : 0; var deleted = _getElementById("clearDelete").checked ? 4 : 0; var clearType = dirty | insert | deleted; var selections = sheet.getSelections(); selections.forEach((selection) => { sheet.clearPendingChanges({ clearType: clearType, row: selection.row, rowCount: selection.rowCount, col: selection.col, colCount: selection.colCount }); }); } else { sheet.clearPendingChanges(); } _getElementByIdtaResult.value = ''; }); _getElementById("btnSetRowCount").addEventListener('click',function () { sheet.setRowCount(60); }); _getElementById("btnSetColumnCount").addEventListener('click',function () { sheet.setColumnCount(16); }); }; function _getElementById(id){ return document.getElementById(id); }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta name="spreadjs culture" content="zh-cn" /> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div class="options-container"> <p>使用下方按钮插入/删除行或更改行/列数。</p> <p>然后,您可以通过点击“获取脏行”、“获取所有脏单元格”等按钮来获取脏(已更改)的行或单元格。</p> <div class="option-row"> <input type="button" id="btnInsertRow" value="插入行" title="在选中行插入新行" /> <input type="button" id="btnDeleteRow" value="删除行" title="删除选中的行" /> </div> <p>如果要按范围清除待处理的更改,请在工作表中选择范围,并在“脏数据”/“插入”/“删除”中选择类型。</p> <div class="option-row"> <div> <input id="clearByRange" type="checkbox"> <label for="clearByRange">按范围清除</label> </div> <div> <input id="clearDirty" type="checkbox"> <label for="clearDirty">脏数据</label> <input id="clearInsert" type="checkbox"> <label for="clearInsert">插入</label> <input id="clearDelete" type="checkbox"> <label for="clearDelete">删除</label> </div> <input type="button" id="btnClearPendingChanges" value="清除待处理更改" title="清除待处理的更改" /> </div> <div class="option-row"> <input type="button" id="btnSetRowCount" value="设置行数" title="将行数设置为60" /> <input type="button" id="btnSetColumnCount" value="设置列数" title="将列数设置为16" /> </div> <div class="option-row"> <input type="button" id="btnGetDirtyRows" value="获取脏行" /> <input type="button" id="btnGetDirtyCells" value="获取所有脏单元格" /> <input type="button" id="btnGetSelectionDirtyCells" value="获取选中区域的脏单元格" /> </div> <div class="option-row"> <input type="button" id="btnGetInsertRows" value="获取插入的行" /> <input type="button" id="btnGetDeleteRows" value="获取删除的行" /> </div> <div class="option-row" style="width:100%;height:80px;"> <textarea id="taResult" style="width:100%;padding:0;float:right;height:80px;background:none"></textarea> </div> </div> </div> </body> </html>
.sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: calc(100% - 280px); height: 100%; overflow: hidden; float: left; } .options-container { float: right; width: 280px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; } .option-row { font-size: 14px; padding: 5px; margin-top: 10px; } p{ padding:2px 10px; background-color:#F4F8EB; } input[type="button"] { width: 100%; margin-bottom: 2px; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; }