概述
本 Demo 展示了 SpreadJS 工作表保护的完整功能,包括工作表保护与取消保护、密码保护机制、单元格锁定设置,以及通过保护选项精细控制用户操作权限。Demo 提供了交互式界面,允许用户动态设置各项保护选项并实时查看效果。
实现思路
初始化工作表数据(销售数据表、数据透视表、筛选器、分组和批注)
设置单元格的锁定状态(通过样式的 locked 属性区分锁定和解锁单元格)
配置保护选项(protectionOptions),定义保护状态下的用户操作权限
启用工作表保护(设置 isProtected 为 true)
绑定交互事件,允许用户通过界面设置密码保护、取消保护以及修改保护选项
代码解析
设置单元格锁定状态
通过样式的 locked 属性控制单元格是否锁定。未锁定的单元格(灰色背景)在保护状态下仍可编辑,锁定的单元格(浅绿色背景)在保护状态下禁止编辑。
配置保护选项
保护选项定义了用户在保护状态下的操作权限。Demo 允许选择锁定和未锁定的单元格、筛选数据、调整行高和使用数据透视表,但禁止排序、调整列宽、编辑对象、插入/删除行列等操作。
启用工作表保护
设置 isProtected 为 true 启用工作表保护。此时,锁定的单元格不可编辑,保护选项生效。
密码保护与取消保护
使用 protect() 方法设置密码保护,hasPassword() 检查是否设置了密码,unprotect() 方法取消保护。如果设置了密码,必须提供正确的密码才能取消保护,方法返回 false 表示密码错误。
运行效果
工作表初始状态为保护状态,灰色背景的单元格可以编辑,浅绿色背景的单元格不可编辑
点击"保护"按钮可以设置密码保护(可选密码),保护图标变为锁定状态
点击"取消保护"按钮可以取消保护,如果设置了密码需要输入正确密码
通过复选框可以动态调整保护选项,点击"设置"按钮应用更改
保护状态下,根据保护选项的设置,用户可以筛选数据、调整行高、使用数据透视表,但无法编辑锁定单元格、调整列宽、插入/删除行列等
API 参考
isProtected 属性
布尔值,控制工作表是否受保护。
protect 方法
password:可选,保护密码
保护工作表,同时自动设置 isProtected 为 true
unprotect 方法
password:可选,取消保护所需的密码
返回值:是否成功取消保护
hasPassword 方法
检查工作表是否设置了保护密码。
protectionOptions 属性
定义工作表保护状态下的用户操作权限。
locked 属性
单元格样式的 locked 属性,控制单元格在工作表保护时是否可编辑。
window.onload = function() {
var spread = new GC.Spread.Sheets.Workbook(_getElementById('ss'), {
sheetCount: 1
});
initSpread(spread);
};
const passwordWrongTip = 'Password is not correct!';
const alreadyProtect = 'The worksheet is already protected!';
const unprotectImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0UlEQVR4nO2YzU7CQBDHNyae9A30qF6NNlFv7QzhqheukN1An4Mbnv14A70YwgERX0XiWRNBKl8eMDGuGWy0LopoS0tl/8kkZLMT/r+dmW1axrS0tAKTYRjziJgCgFNEvAKAR0Tsub9pLUV72DTKsqw9ALhGRDkqaA/tZdOifD4/h4j7PxlX4gURC5QbtX/2B/PeKERqnlrBPU1vi/QR8dCyrO1kMrlAYZrmDgAcAcCTWgkA2I3EPA2j2vMAcAMA69/lJBKJDQC4VWfCiGKw6UZRT36UeU/e5heVSLGw5V6LXhMHv8g9VuBPWNhCxJoCsDVuLs2EkltjYct9QL2bME1zcdxc2qsA9FjYUq/EsPN9SwOgrsD4yuVyy5zzIue8K4SQkwz+9h8lzvlqkOadSRsXwyBOJpNZ8g1AJx+2efEBcRYEQDdCgLZvgKjMCzc0gNAVELqFfGkmW8i2bXl5cS7bTkO2mnVZrZQHa7EBqFbKUj73PwWtxQag7TSGAGgtNgCtZn0I4OH+Lj4A1bi3kG3bA8NUiVgOsQgwmAYQugJSt9Cst1An7q+UpQgBir4B0un0mhCiGYF5J5vNrrAgRN9n6BNHSO3UoZMPzLyW1j/XK1zJDTLJ864hAAAAAElFTkSuQmCC';
const protectImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB10lEQVR4nO2Yz0rDQBDGB0Ev+gZ6VK+iAfWWzJRe9dL3qfVQlWwR/7yBXqT0JepD2Nar0h4s2ERq3YArW4LGrdZq0qSx+8FACDvw/XZmNmQBtLS0IpNhGLNElEPESyK6QcQnInL9Z/kuJ9fAJMqyrF1EbBCRGBZyjVwLk6J8Pj9DRAc/GVfilYiKMjdp//AH88EoJmpetoK/m8EW6RHRqWVZW9lsdl6GaZrbiHiGiC9qJRBxJxHzchjVnkfEO0Rc+y4nk8msI+K9OhNGEoMtTxR154eZD+RtfFGJHMQt/1gMmjj5Re65An8BcYuIagrA5qi5ciaU3BrELf8D9W7CNM2FUXPlWgXAhbilHolx54eWBiBdgdElDmGJMyhzBo7HQIwzOAOHM6j0jmAlOvM2tMdt3FNBbGiLEiyGBpA7H7d576MaV1EAOAkCPIYGSMq854cG8HQFQLdQKE1nCx3Pie71vnBbDeE266JbLfTfpQagWy0Ix3E+RR8iLQBuqzEA4LZuUwTQrA8CNGtpb6G9lA1xtdCvRCqH2IswQAMwXQGhW2iqW4jb0En1LyVnUEkQoBwaoMdglTN4iN28De3nEixDFJL3M/KKI4524jZ05M5HZl5L65/rDc6nQtkkwmMVAAAAAElFTkSuQmCC';
function initSpread(spread) {
var sheet = spread.getActiveSheet();
spread.suspendPaint();
var salesData = [
['Salesperson', 'Region', 'Sales'],
['Joe', 'North', 3220],
['Robert', 'South', 3214],
['Michelle', 'East', 4233],
['Erich', 'West', 1235],
['Dafna', 'North', 3241],
['Rob', 'South', 2135],
['Joe', 'North', 2130],
['Robert', 'South', 4510],
['Michelle', 'East', 4550],
['Erich', 'West', 3250],
['Dafna', 'North', 3310],
['Rob', 'South', 2350],
['Joe', 'North', 2345],
['Robert', 'South', 2370],
['Michelle', 'East', 3980]
];
sheet.setArray(0, 0, salesData);
sheet.setColumnWidth(0, 100);
sheet.setColumnWidth(1, 100);
sheet.setColumnWidth(2, 80);
//unlocked cells
var style = new GC.Spread.Sheets.Style();
style.locked = false;
style.backColor = '#C3C3C3';
sheet.setStyle(-1, 8, style);
sheet.setStyle(-1, 9, style);
sheet.setStyle(15, -1, style);
sheet.setStyle(16, -1, style);
sheet.setStyle(8, 2, style);
//locked cells
var style2 = new GC.Spread.Sheets.Style();
style2.locked = true;
style2.backColor = '#F4F8EB';
sheet.setStyle(13, -1, style2);
sheet.setStyle(18, 8, style2);
sheet.setStyle(0, 0, style2)
sheet.setStyle(0, 1, style2);
sheet.setStyle(0, 2, style2);
let pivotTable = sheet.pivotTables.add("pivotTable1", 'Sheet1!A1:C16', 1, 11, GC.Spread.Pivot.PivotTableLayoutType.tabular, GC.Spread.Pivot.PivotTableThemes.medium8);
pivotTable.suspendLayout();
pivotTable.add("salesperson", "Salesperson", GC.Spread.Pivot.PivotTableFieldType.rowField);
pivotTable.add("region", "Region", GC.Spread.Pivot.PivotTableFieldType.columnField);
pivotTable.add("sales", "Sales", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.sum);
pivotTable.resumeLayout();
pivotTable.autoFitColumn();
var filter = new GC.Spread.Sheets.Filter.HideRowFilter(new GC.Spread.Sheets.Range(1, 0, 100, 2));
sheet.rowFilter(filter);
sheet.comments.add(5, 4, 'locked comment');
sheet.comments.add(22, 4, 'unlocked comment');
sheet.comments.get(5, 4).locked(true).displayMode(1);
sheet.comments.get(22, 4).locked(false).displayMode(1).lockText(false);
var _commandManager = spread.commandManager();
_commandManager.execute({
cmd: "outlineColumn",
sheetName: sheet.name(),
index: 11,
count: 3
});
_commandManager.execute({
cmd: "outlineRow",
sheetName: sheet.name(),
index: 18,
count: 3
});
spread.resumePaint();
var option = {
allowSelectLockedCells:true,
allowSelectUnlockedCells:true,
allowFilter: true,
allowSort: false,
allowResizeRows: true,
allowResizeColumns: false,
allowEditObjects: false,
allowDragInsertRows: false,
allowDragInsertColumns: false,
allowInsertRows: false,
allowInsertColumns: false,
allowDeleteRows: false,
allowDeleteColumns: false,
allowOutlineColumns: false,
allowOutlineRows: false,
allowUsePivotTable: true,
};
sheet.options.protectionOptions = option;
sheet.options.isProtected = true;
option = sheet.options.protectionOptions;
_getElementById('chkSelectLockedCells').checked = option.allowSelectLockedCells;
_getElementById('chkSelectUnlockedCells').checked = option.allowSelectUnlockedCells;
_getElementById('chkAllowSort').checked = option.allowSort;
_getElementById('chkAllowFilter').checked = option.allowFilter;
_getElementById('chkAllowResizeRows').checked = option.allowResizeRows;
_getElementById('chkAllowResizeColumns').checked = option.allowResizeColumns;
_getElementById('chkAllowEditObjects').checked = option.allowEditObjects;
_getElementById('chkAllowDragInsertRows').checked = option.allowDragInsertRows;
_getElementById('chkAllowDragInsertColumns').checked = option.allowDragInsertColumns;
_getElementById('chkAllowInsertRows').checked = option.allowInsertRows;
_getElementById('chkAllowInsertColumns').checked = option.allowInsertColumns;
_getElementById('chkAllowDeleteRows').checked = option.allowDeleteRows;
_getElementById('chkAllowDeleteColumns').checked = option.allowDeleteColumns;
_getElementById('chkallowOutlineColumns').checked = option.allowOutlineColumns;
_getElementById('chkallowOutlineRows').checked = option.allowOutlineRows;
_getElementById('chkAllowUsePivotTables').checked = option.allowUsePivotTable;
const protectStatus = _getElementById('protectStatus');
const passwordInput = _getElementById('protectPassword')
protectStatus.src = sheet.options.isProtected ? protectImg : unprotectImg;
let protectBtn = _getElementById('protectBtn'), unprotectBtn = _getElementById('unprotectBtn');
protectBtn.disabled = true;
protectBtn.addEventListener('click', function () {
sheet = spread.getActiveSheet();
if (sheet.options.isProtected) {
alert(alreadyProtect);
return ;
}
const password = passwordInput.value;
passwordInput.value = '';
sheet.protect(password);
protectStatus.src = protectImg;
protectBtn.disabled = true;
unprotectBtn.disabled = false;
});
unprotectBtn.addEventListener('click', function () {
const password = passwordInput.value;
sheet = spread.getActiveSheet();
if (sheet.hasPassword()) {
let success = sheet.unprotect(password);
if (!success) {
alert(passwordWrongTip);
return ;
}
} else {
sheet.unprotect();
}
passwordInput.value = '';
protectStatus.src = unprotectImg;
protectBtn.disabled = false;
unprotectBtn.disabled = true;
})
_getElementById('setProtectionOptions').addEventListener('click', function() {
var option = {
allowSelectLockedCells: _getElementById('chkSelectLockedCells').checked,
allowSelectUnlockedCells: _getElementById('chkSelectUnlockedCells').checked,
allowSort: _getElementById('chkAllowSort').checked,
allowFilter: _getElementById('chkAllowFilter').checked,
allowResizeRows: _getElementById('chkAllowResizeRows').checked,
allowResizeColumns: _getElementById('chkAllowResizeColumns').checked,
allowEditObjects: _getElementById('chkAllowEditObjects').checked,
allowDragInsertRows: _getElementById('chkAllowDragInsertRows').checked,
allowDragInsertColumns: _getElementById('chkAllowDragInsertColumns').checked,
allowInsertRows: _getElementById('chkAllowInsertRows').checked,
allowInsertColumns: _getElementById('chkAllowInsertColumns').checked,
allowDeleteRows: _getElementById('chkAllowDeleteRows').checked,
allowDeleteColumns: _getElementById('chkAllowDeleteColumns').checked,
allowOutlineColumns: _getElementById('chkallowOutlineColumns').checked,
allowOutlineRows: _getElementById('chkallowOutlineRows').checked,
allowUsePivotTable: _getElementById('chkAllowUsePivotTables').checked,
};
var sheet = spread.getActiveSheet();
sheet.options.protectionOptions = option;
});
}
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-shapes/dist/gc.spread.sheets.shapes.min.js"
type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-pivot-addon/dist/gc.spread.pivot.pivottables.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">
<br />
<div class="option-row" style="height: 25px">
<img id="protectStatus" style="height: 25px" />
</div>
<div class="option-row">
<label for="protectPassword">密码(可选)</label>
<input id="protectPassword" type="password" />
<input type="button" value="保护" id="protectBtn" style="width:100px;" />
<input type="button" value="取消保护" id="unprotectBtn" style="width:100px;" />
</div>
<br />
<div class="option-row">
<label>使用以下保护选项限制用户对工作表的编辑操作。</label>
</div>
<div>
<div class="option-row">
<input id="chkSelectLockedCells" type="checkbox" checked="checked" />
<label for="chkSelectLockedCells" class="sizedLabel">选择锁定的单元格</label>
</div>
<div class="option-row">
<input id="chkSelectUnlockedCells" type="checkbox" checked="checked" />
<label for="chkSelectUnlockedCells" class="sizedLabel">选择未锁定的单元格</label>
</div>
<div class="option-row">
<input id="chkAllowSort" type="checkbox" />
<label for="chkAllowSort" class="sizedLabel">排序</label>
</div>
<div class="option-row">
<input id="chkAllowFilter" type="checkbox" />
<label for="chkAllowFilter" class="sizedLabel">筛选</label>
</div>
<div class="option-row">
<input id="chkAllowResizeRows" type="checkbox" />
<label for="chkAllowResizeRows" class="sizedLabel">调整行大小</label>
</div>
<div class="option-row">
<input id="chkAllowResizeColumns" type="checkbox" />
<label for="chkAllowResizeColumns" class="sizedLabel">调整列大小</label>
</div>
<div class="option-row">
<input id="chkAllowEditObjects" type="checkbox" />
<label for="chkAllowEditObjects" class="sizedLabel">编辑对象</label>
</div>
<div class="option-row">
<input id="chkAllowDragInsertRows" type="checkbox" />
<label for="chkAllowDragInsertRows" class="sizedLabel">拖动插入行</label>
</div>
<div class="option-row">
<input id="chkAllowDragInsertColumns" type="checkbox" />
<label for="chkAllowDragInsertColumns" class="sizedLabel">拖动插入列</label>
</div>
<div class="option-row">
<input id="chkAllowInsertRows" type="checkbox" />
<label for="chkAllowInsertRows" class="sizedLabel">插入行</label>
</div>
<div class="option-row">
<input id="chkAllowInsertColumns" type="checkbox" />
<label for="chkAllowInsertColumns" class="sizedLabel">插入列</label>
</div>
<div class="option-row">
<input id="chkAllowDeleteRows" type="checkbox" />
<label for="chkAllowDeleteRows" class="sizedLabel">删除行</label>
</div>
<div class="option-row">
<input id="chkAllowDeleteColumns" type="checkbox" />
<label for="chkAllowDeleteColumns" class="sizedLabel">删除列</label>
</div>
<div class="option-row">
<input id="chkallowOutlineRows" type="checkbox" />
<label for="chkallowOutlineRows" class="sizedLabel">操作行分级显示</label>
</div>
<div class="option-row">
<input id="chkallowOutlineColumns" type="checkbox" />
<label for="chkallowOutlineColumns" class="sizedLabel">操作列分级显示</label>
</div>
<div class="option-row">
<input id="chkAllowUsePivotTables" type="checkbox" />
<label for="chkAllowUsePivotTables" class="sizedLabel">使用数据透视表</label>
</div>
<div class="option-row">
<input type="button" value="设置" id="setProtectionOptions" style="width:100px;" />
</div>
<div class="option-row">
<label>选择保护选项,然后点击“设置”</label>
</div>
</div>
</div>
</div>
</body>
</html>
.sizedLabel {
display: inline-block;
width: 180px;
}
.colorLabel {
background-color: #F4F8EB;
}
.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;
}
label {
margin-bottom: 6px;
}
input {
padding: 4px 6px;
}
input[type=button] {
margin-top: 6px;
}
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}