背景
- 表格是中后台系统最常见的数据呈现方式
- 一般中后台系统都会有权限管控的需求,包括功能权限和数据权限
- 对表格来说,数据权限包括行权限和列权限
- 行权限:是否对单条数据有可见权限,例如:一个人只能看到自己创建的数据而看不到其他人的数据。行权限基本上是由服务端控制
- 列权限:是否对某些特定的字段有可见权限,例如:普通用户看不到「成本价」这个字段,而管理员可以看到,尽管他们都能看到同样的 10 个商品。通常情况下,列权限需要前后端配合实现
遇到的问题
在无权限控制需求时,我们实现了一个表格页面如下
import React from 'react';
import { Button, Table as AntdTable } from 'antd';
// 模拟数据
const dataSource = new Array(8).fill(null).map((item, index) => ({
id: index,
columnA: `a${index}`,
columnB: `b${index}`,
}));
IProps {}
const Table: React.FC<IProps> = () => {
const columns = [
{
dataIndex: 'id',
},
{
dataIndex: 'columnA',
},
{
dataIndex: 'columnB',
},
{
dataIndex: 'actions',
render: () => (
<React.Fragment>
<Button>act1</Button>
<Button>act2</Button>
</React.Fragment>
),
},
];
return <AntdTable dataSource={dataSource} columns={columns} />;
};
export default Table;
某一天,PM 小姐姐告诉我们,这里的数据太敏感了,需要做权限区分:只有角色为 admin
的用户才能看到 columnB
这一列。
我们按着原来的代码,啃哧啃哧几下就搞定了,只修改了 columns 的实现
const columns = [
{
dataIndex: 'id',
},
{
dataIndex: 'columnA'
}
];
if (window.role === 'admin') {
columns.push({ dataIndex: 'columnB' });
}
columns.push({
dataIndex: 'actions',
render: () => (
<React.Fragment>
<Button>act1</Button>
<Button>act2</Button>
</React.Fragment>
),
});
日子安稳了没几天,PM 小姐姐又找上门来了,说 columnA
columnB
的数据都有一定的敏感度,我们的系统三个角色包括 admin
、user
和 super
,其中
admin
可以看到所有列user
可以看到columnA
,不能看columnB
super
可以看到columnB
,不能看columnA
没办法,我们只能再对上面的实现做些修改
const columns = [
{
dataIndex: 'id',
},
{
dataIndex: 'columnA'
}
];
if (window.role === 'user' || window.role === 'admin') {
columns.push({ dataIndex: 'columnA' });
}
if (window.role === 'super' || window.role === 'admin') {
columns.push({ dataIndex: 'columnB' });
}
columns.push({
dataIndex: 'actions',
render: () => (
<React.Fragment>
<Button>act1</Button>
<Button>act2</Button>
</React.Fragment>
),
});
一顿操作下来,代码已经变得十分难看,最重要的是很难维护了,如果以后又多了一些角色,新的需求必然导致代码中充斥着难以理解的判断逻辑,我们必须找到合适的方案去处理掉这个问题
解决方案
我们不妨来思考一下刚刚的实现方案问题在哪里
- 本来是一个表单页面的组件,却因为权限控制的需求,杂糅了「非 UI 实现」的逻辑,恰恰违背了「单一职责原则(SRP)」
- 未来的需求,有可能是调整权限,也有可能是修改 UI 实现,都需要在同一个地方去处理,很容易产生意料之外的问题,这违反了「关注点分离(SoC)」
我们要找到一种方式,让「权限控制」和「UI 实现」解耦
我们通过组件,把这个页面分成两层,分别实现权限控制和 UI 逻辑
对于 UI 逻辑部分,我们不需要关注权限,所以在这一层,我们先默认用户拥有所有权限,把所有 UI 逻辑部分都实现完整,再通过一个属性,来最终过滤出需要展示的列
interface IProps {
displayColumns?: string[];
}
const Table: React.FC<IProps> = ({ displayColumns }) => {
// 定义所有的列
const columns = [
{
dataIndex: 'id',
},
{
dataIndex: 'columnA',
},
{
dataIndex: 'columnB',
},
{
dataIndex: 'actions',
render: () => (
<React.Fragment>
<Button>act1</Button>
<Button>act2</Button>
</React.Fragment>
),
},
];
const finalColumns = displayColumns
? columns.filter(item => displayColumns.join(',').includes(item.dataIndex))
: columns;
return <AntdTable dataSource={dataSource} columns={finalColumns} />;
};
在权限控制部分,我们根据角色判断需要展示的列之后,通过 props 传递给 UI 层
import Table from './Table';
declare global {
interface Window {
role: any;
}
}
const getDisplayColumns = () => {
if (window.role === 'user') {
return ['id', 'columnA', 'actions'];
}
if (window.role === 'super') {
return ['id', 'columnB', 'actions'];
}
if (window.role === 'admin') {
return ['id', 'columnA', 'columnB', 'actions'];
}
return [];
};
const TablePage: React.FC = () => {
return (
<Table
displayColumns={getDisplayColumns()}
/>
);
};
export default TablePage;
我们再将上面的 getDisplayColumns 方法优化一下,毕竟过多的 if 条件会让代码显得很 low
const roleColumnsMap: any = {
user: ['id', 'columnA', 'actions'],
super: ['id', 'columnB', 'actions'],
admin: ['id', 'columnA', 'columnB', 'actions'],
};
const getDisplayColumns = () => roleColumnsMap[window.role] || [];
分层,帮我们实现了「关注点分离」,降低了修改的「心智负担」
又过了几天,PM 小姐姐提出了新的需求,这次不是数据权限的问题,是操作权限的问题,即
admin
可以看到所有操作user
可以看到act1
,不能看act2
super
可以看到act2
,不能看act1
同样的,我们完全可以按照上面的思路来处理
- 权限层
import Table from './Table';
declare global {
interface Window {
role: any;
}
}
const roleColumnsMap: any = {
user: ['id', 'columnA', 'actions'],
super: ['id', 'columnB', 'actions'],
admin: ['id', 'columnA', 'columnB', 'actions'],
};
const roleActionsMap: any = {
user: ['act1'],
super: ['act2'],
admin: ['act1', 'act2'],
};
window.role = 'admin';
const getDisplayColumns = () => roleColumnsMap[window.role] || [];
const getDisplayActions = () => roleActionsMap[window.role] || [];
const TablePage: React.FC = () => {
return (
<Table
displayColumns={getDisplayColumns()}
displayActions={getDisplayActions()}
/>
);
};
export default TablePage;
- UI 层
import React from 'react';
import { Button, Table as AntdTable } from 'antd';
interface IProps {
displayColumns?: string[];
displayActions?: string[];
}
// 模拟数据
const dataSource = new Array(8).fill(null).map((item, index) => ({
id: index,
columnA: `a${index}`,
columnB: `b${index}`,
}));
const Table: React.FC<IProps> = ({ displayColumns, displayActions }) => {
// 定义所有的操作
const actions = [
{
key: 'act1',
rc: <Button>act1</Button>,
},
{
key: 'act2',
rc: <Button>act2</Button>,
},
];
const finalActions = displayActions
? actions.filter(item => displayActions.join(',').includes(item.key))
: actions;
// 定义所有的列
const columns = [
{
dataIndex: 'id',
},
{
dataIndex: 'columnA',
},
{
dataIndex: 'columnB',
},
{
dataIndex: 'actions',
render: () => finalActions.map(action => action.rc),
},
];
const finalColumns = displayColumns
? columns.filter(item => displayColumns.join(',').includes(item.dataIndex))
: columns;
return <AntdTable dataSource={dataSource} columns={finalColumns} />;
};
export default Table;
至此,任何的权限调整,都被限定在了与 UI 实现无关的地方。未来,如果服务端支持,还可以将每个角色所对应的权限通过 API 的方式获取,从而加大灵活程度,可以在不用改代码的情况下增加角色、调整权限。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!