不定日拱卒-優雅地實現列權限

不定日拱卒:分享平常開發過程當中的一些小技巧,爲更多人提供相似問題的解決方案react

背景

  • 表格是中後臺系統最多見的數據呈現方式
  • 通常中後臺系統都會有權限管控的需求,包括功能權限和數據權限
  • 對錶格來講,數據權限包括行權限和列權限
    • 行權限:是否對單條數據有可見權限,例如:一我的只能看到本身建立的數據而看不到其餘人的數據。行權限基本上是由服務端控制
    • 列權限:是否對某些特定的字段有可見權限,例如:普通用戶看不到「成本價」這個字段,而管理員能夠看到,儘管他們都能看到一樣的 10 個商品。一般狀況下,列權限須要先後端配合實現

遇到的問題

假設咱們的角色是經過 role 存放在 window 對象中的後端

在無權限控制需求時,咱們實現了一個表格頁面以下markdown

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 這一列。antd

咱們按着原來的代碼,啃哧啃哧幾下就搞定了,只修改了 columns 的實現oop

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

沒辦法,咱們只能再對上面的實現作些修改ui

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>
    ),
  });
複製代碼

一頓操做下來,代碼已經變得十分難看,最重要的是很難維護了,若是之後又多了一些角色,新的需求必然致使代碼中充斥着難以理解的判斷邏輯,咱們必須找到合適的方案去處理掉這個問題spa

解決方案

咱們不妨來思考一下剛剛的實現方案問題在哪裏code

  • 原本是一個表單頁面的組件,卻由於權限控制的需求,雜糅了「非 UI 實現」的邏輯,偏偏違背了「單一職責原則(SRP)」
  • 將來的需求,有多是調整權限,也有多是修改 UI 實現,都須要在同一個地方去處理,很容易產生意料以外的問題,這違反了「關注點分離(SoC)」

咱們要找到一種方式,讓「權限控制」和「UI 實現」解耦orm

沒有什麼是分層解決不了的

咱們經過組件,把這個頁面分紅兩層,分別實現權限控制和 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 的方式獲取,從而加大靈活程度,能夠在不用改代碼的狀況下增長角色、調整權限。

有時候,咱們能夠適當轉變一下思路,採用一些「模式」來優雅地解決問題

相關文章
相關標籤/搜索