最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 不定日拱卒-优雅地实现列权限

    正文概述 掘金(Splendour)   2021-02-09   402

    背景

    • 表格是中后台系统最常见的数据呈现方式
    • 一般中后台系统都会有权限管控的需求,包括功能权限和数据权限
    • 对表格来说,数据权限包括行权限和列权限
      • 行权限:是否对单条数据有可见权限,例如:一个人只能看到自己创建的数据而看不到其他人的数据。行权限基本上是由服务端控制
      • 列权限:是否对某些特定的字段有可见权限,例如:普通用户看不到「成本价」这个字段,而管理员可以看到,尽管他们都能看到同样的 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 的数据都有一定的敏感度,我们的系统三个角色包括 adminusersuper,其中

    • 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介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元