基于Luckysheet的钩子函数实现不同区域的权限控制

需求

由于同一张表格的不同区域需要不同角色进行编辑,如果不对编辑区域加以限制,那么极有可能出现错填、恶意改写等情况。因此为了保证数据的准确性和减少数据之间的干扰,需要开发不同区域的权限控制功能。

准备工作和思路

经过实验,首先发现Luckysheet自带表格保护功能,但是它所实现的保护粒度是基于表格的,想要基于不同角色来保护单元格是有开发成本的。并且自身体验下来感觉并不易用,因此转变思路,发现了Luckysheet的一个特性:钩子函数(hook)。

钩子函数应用于二次开发时,会在各个常用鼠标或者键盘操作时植入钩子,调用开发者传入的函数,起到扩展Luckysheet功能的作用。这里想到:可以在进入单元格编辑模式之前触发,如果需要编辑的区域不在权限内,则不提供编辑权限。

Luckysheet钩子函数官方文档

实现

使用cellEditBefore钩子

官方定义的cellEditBefore钩子如下:

cellEditBefore

而钩子函数需要我们在创建表格的时候就定义好,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
window.luckysheet.create({
container: 'luckysheet', // 设定DOM容器的id
title: '默认Excel表格', // 表 头名
lang: 'zh', // 中文
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按
data: data,
hook: {
cellEditBefore: function (range) {
let role = window.sessionStorage.getItem('role');
let perm = JSON.parse(window.sessionStorage.getItem('perm'));
if (role === 'admin' || role === 'TemplateEditor') return true;
if (perm[role] === undefined) {
alert('您没有编辑权限, 请联系管理员!');
return false;
}
let range_ = range[0];
let filter = perm[role].filter(x => x.column_focus === range_.column_focus && x.row_focus === range_.row_focus);
if (filter.length === 0) {
alert('您没有编辑权限, 请联系管理员!');
return false;
}
},
}
})

至于cellEditBefore的具体业务代码怎么写就具体分析了,我这里的大概思路就是判断当前登录角色和待编辑区域角色是否一致了。

如何定义权限

那么我们要如何自己设置不同区域的权限呢?我这里自定义了一个选区方法,可以通过手动选择选区并指定角色,为选中区域分配对应角色的权限。

选区

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
submitFormPermission() {
const role = this.dataFormPermission.role;
let selectionRangeList = luckysheet.getLuckysheetfile()[0].luckysheet_select_save;
if (typeof this.permissionDict === 'string') {
this.permissionDict = JSON.parse(this.permissionDict);
}
for (let i in selectionRangeList) {
if (this.permissionDict.hasOwnProperty(role)) {
let filter = this.permissionDict[role].filter(x => x.column_focus === selectionRangeList[i].column_focus && x.row_focus === selectionRangeList[i].row_focus);
if (filter.length === 0) {
this.permissionDict[role].push(selectionRangeList[i]);
}
} else {
this.permissionDict[role] = [selectionRangeList[i]];
}
let value = this.selectionData;
value['m'] = this.dataFormPermission.content;
value['v'] = this.dataFormPermission.content;
value['fc'] = this.roleColorDict.filter(x => x.value === this.dataFormPermission.role)[0].label;
luckysheet.setCellValue(selectionRangeList[i].row_focus, selectionRangeList[i].column_focus, value);
}
for (let i in selectionRangeList) {
for (let j in this.permissionDict) {
if (role !== j) {
this.permissionDict[j] = this.permissionDict[j].filter(x => x.column_focus !== selectionRangeList[i].column_focus || x.row_focus !== selectionRangeList[i].row_focus);
}
}
}
this.$modal.msgSuccess("选区设置成功");
this.openPermission = false;
},

其中,用到了luckysheet.getLuckysheetfile()[0].luckysheet_select_save方法获取到了选区的数据,使用luckysheet.setCellValue设置单元格内容。

setCellValue

这里的权限数据是类似于json格式的数据,我们将这些数据也保存起来。

权限数据

其中,第一层(common)是我系统中的角色value。在这个角色下面,记录着该角色的所有权限区域,这些区域也是json格式的数据,其中比较关键的值就是row_focuscolumn_focus,就是一个单元格的焦点坐标,也可以理解为精准坐标,是我们用来对比权限是否一致的重要参数。

如何自适应区域

当我们插入或删除行时,希望对应区域的权限也能自适应的扩展或减少,具体体现为:在已有权限区域内或边界插入行时,插入的行自动添加当前区域的权限;删除行时,将当前行及以下的权限上移。这里我们同样用到钩子函数的updated来监听表格变化,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
updated: function (operate) {
if (operate.type === 'addRC') {
window.sessionStorage.setItem('operate', 'addRC');
let perm = JSON.parse(window.sessionStorage.getItem('perm'));
let ctrlValue = operate.ctrlValue;
let row = ctrlValue.index;
let len = ctrlValue.len;
for (let i in perm) {
for (let j in perm[i]) {
if (perm[i][j].row_focus >= row) {
for (let k = 1; k <= len; k++) {
let temp = JSON.parse(JSON.stringify(perm[i][j]));
temp.row_focus = temp.row_focus + k;
temp.row[0] = temp.row[0] + k;
temp.row[1] = temp.row[1] + k;
perm[i].push(temp);
}
}
}
}
window.sessionStorage.setItem('perm', JSON.stringify(perm));
}
if (operate.type === 'delRC') {
let perm = JSON.parse(window.sessionStorage.getItem('perm_data'));
let ctrlValue = operate.ctrlValue;
let row = ctrlValue.index;
let len = ctrlValue.len;
for (let i in perm) {
for (let j in perm[i]) {
if (perm[i][j].row_focus === row) {
perm[i].splice(j, 1);
}
if (perm[i][j].row_focus > row) {
perm[i][j].row_focus = perm[i][j].row_focus - len;
perm[i][j].row[0] = perm[i][j].row[0] - len;
perm[i][j].row[1] = perm[i][j].row[1] - len;
}
}
}
window.sessionStorage.setItem('perm_data', JSON.stringify(perm));
}
},

其中,只要表格内容发生变化,我们就能从operate参数中监听到变化的数据。这里的operate.type分别为addRC(插入行)和delRC(删除行)。这里的业务代码很具体,不细讲。

至此,我们就理顺了实现不同区域对应不同角色权限,且权限根据区域自适应的功能思路。

当我们尝试编辑我们权限之外的区域时,会弹出警告。

警告