概述
本 Demo 展示了如何在集算表的批量模式下使用自定义函数处理远程请求。通过将 remote 配置项设置为函数而不是对象,开发者可以自定义请求逻辑,如添加请求头、统一错误处理等。Demo 使用封装的 sendRequest 函数统一处理请求,并在批量模式下一次性提交所有数据变更。
实现思路
封装通用的请求函数 sendRequest,统一设置请求方法、请求头和 JSON 序列化
初始化数据管理器,创建数据表并配置 remote 选项为自定义函数
为 read 操作配置 GET 请求,用于获取初始数据
为 batch 操作配置 POST 请求,用于批量提交所有数据变更
启用批量模式(batch: true),使所有修改暂存在本地
创建集算表并添加内置行操作按钮(删除、保存、重置)
绑定视图到集算表,为用户操作按钮绑定事件处理器
代码解析
封装通用请求函数
这段代码封装了通用的请求逻辑。函数接收 url 和 options 参数,设置默认请求方法为 POST,添加 JSON 内容类型请求头,并对请求体进行 JSON 序列化。如果响应成功则返回 JSON 数据,否则抛出错误信息。开发者可以在此函数中添加认证头、日志记录等自定义逻辑。
配置数据表的远程请求选项
remote.read 函数使用 GET 请求获取初始数据,返回封装数据的 Promise。remote.batch 函数接收所有数据变更数组,并通过一次 POST 请求提交到服务器。batch: true 启用批量模式,使所有修改暂存在本地,直到调用 submitChanges() 方法。
添加内置行操作
使用 BuiltInRowActions 枚举添加内置的行操作按钮:
removeRow:删除行
saveRow:保存行中的数据变化
resetRow:重置行中的数据变化
这些操作按钮会显示在集算表的行头上,方便用户对单行数据进行操作。
绑定用户操作事件
submitChanges() 方法将所有暂存的变更通过 remote.batch 函数一次性提交到服务器。cancelChanges() 方法则放弃所有未提交的修改,恢复到初始状态。
运行效果
表格加载后显示员工数据(ID、姓名、电话、备注)
用户可以编辑单元格内容,修改会暂存在本地,不会立即提交
选择一行后,可以点击右侧的行操作按钮:删除行、保存行或重置行
点击"提交变更"按钮,所有暂存的修改会通过一次 POST 请求批量提交到服务器
点击"放弃变更"按钮,所有未提交的修改会被丢弃,表格恢复到上次提交的状态
/*REPLACE_MARKER*/
/*DO NOT DELETE THESE COMMENTS*/
var tableName = "DefineEmployee";
var baseApiUrl = getBaseApiUrl();
var apiUrl = baseApiUrl + "/" + tableName;
var batchApiUrl = baseApiUrl + "/" + tableName + 'Collection';
var tablesheetName = 'MyTableSheet';
var spread, sheet, view, selections, table;
window.onload = function () {
spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss"), { sheetCount: 0 });
initSpread(spread);
bindEvents();
};
function sendRequest(url, options) {
options.method = options.method || 'POST';
options.headers = { 'Content-Type': 'application/json; charset=utf-8' };
if (options.body) {
options.body = JSON.stringify(options.body);
}
return fetch(url, options).then(resp => {
if (resp.ok) {
return resp.json();
} else {
throw resp.statusText;
}
});
}
function initSpread(spread) {
spread.suspendPaint();
spread.clearSheets();
spread.options.autoFitType = GC.Spread.Sheets.AutoFitType.cellWithHeader;
//init a data manager
var dataManager = spread.dataManager();
var myTable = dataManager.addTable("myTable", {
remote: {
read: function () {
return sendRequest(apiUrl, { method: 'GET' });
},
batch: function (changes) {
return sendRequest(batchApiUrl, { body: changes });
}
},
batch: true,
schema: {
columns: {
"Id": { dataType: "number" },
"LastName": { dataType: "string" },
"FirstName": { dataType: "string" },
"HomePhone": { dataType: "string" },
"Notes": { dataType: "string" }
}
}
});
table = myTable;
//init a table sheet
sheet = spread.addSheetTab(0, tablesheetName, GC.Spread.Sheets.SheetType.tableSheet);
var rowActions = GC.Spread.Sheets.TableSheet.BuiltInRowActions;
var options = sheet.rowActionOptions();
options.push(
rowActions.removeRow,
rowActions.saveRow,
rowActions.resetRow,
);
sheet.rowActionOptions(options);
//bind a view to the table sheet
myTable.fetch().then(function () {
view = myTable.addView("myView", [
{ value: "Id", width: 50, caption: "ID" },
{ value: "FirstName", width: 100, caption: "First Name" },
{ value: "LastName", width: 100, caption: "Last Name" },
{ value: "HomePhone", width: 100, caption: "Phone" },
{ value: "Notes", width: 100, caption: "Notes" }
]);
sheet.setDataView(view);
});
spread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, args) {
selections = args.newSelections;
});
spread.resumePaint();
}
function bindEvents() {
var removeButton = document.getElementById('remove');
removeButton.addEventListener('click', function () {
traverseSelectionsRowsWithOperation(function (row) {
sheet.removeRow(row);
});
});
var saveButton = document.getElementById('save');
saveButton.addEventListener('click', function () {
traverseSelectionsRowsWithOperation(function (row) {
sheet.saveRow(row);
});
});
var resetButton = document.getElementById('reset');
resetButton.addEventListener('click', function () {
traverseSelectionsRowsWithOperation(function (row) {
sheet.resetRow(row);
});
});
var saveAllButton = document.getElementById('save-all');
saveAllButton.addEventListener('click', function () {
spread.commandManager().SaveAll.execute(spread, { sheetName: tablesheetName });
});
var submitButton = document.getElementById('submit');
submitButton.addEventListener('click', function () {
sheet.submitChanges();
});
var discardButton = document.getElementById('discard');
discardButton.addEventListener('click', function () {
sheet.cancelChanges();
});
}
function traverseSelectionsRowsWithOperation(operation) {
if (selections) {
for (var i = 0; i < selections.length; i++) {
var selection = selections[i];
var row = selection.row;
var rowCount = selection.rowCount;
for (var r = row + rowCount - 1; r >= row; r--) {
operation(r);
}
}
}
}
function getBaseApiUrl() {
return window.location.href.match(/http.+spreadjs\/SpreadJSTutorial\//)[0] + 'server/api';
}
<!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">
<!-- Promise Polyfill for IE, https://www.npmjs.com/package/promise-polyfill -->
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<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-tablesheet/dist/gc.spread.sheets.tablesheet.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 id="options-container" class="options-container">
<fieldset>
<legend>活动行操作</legend>
<div class="field-line">
<input id="remove" type="button" value="删除">
</div>
<div class="field-line">
<input id="save" type="button" value="保存">
</div>
<div class="field-line">
<input id="reset" type="button" value="重置">
</div>
</fieldset>
<fieldset>
<legend>保存所有行</legend>
<div class="field-line">
<input id="save-all" type="button" value="全部保存">
</div>
</fieldset>
<fieldset>
<legend>批量操作</legend>
<div class="field-line">
<input type="button" value="提交" id="submit">
</div>
<div class="field-line">
<input type="button" value="放弃" id="discard">
</div>
</fieldset>
</div>
</div>
</html>
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
fieldset {
padding: 6px;
margin: 0;
margin-top: 10px;
}
.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;
}
fieldset span,
fieldset input,
fieldset select {
display: inline-block;
text-align: left;
}
fieldset input[type=text] {
width: calc(100% - 58px);
}
fieldset input[type=button] {
width: 100%;
text-align: center;
}
fieldset select {
width: calc(100% - 50px);
}
.field-line {
margin-top: 4px;
}
.field-inline {
display: inline-block;
vertical-align: middle;
}
fieldset label.field-inline {
width: 100px;
}
fieldset input.field-inline {
width: calc(100% - 100px - 12px);
}
.required {
color: red;
font-weight: bold;
}
#fields {
display: none;
}
#fields.show {
display: block;
}