瀏覽代碼

rm ant-design-pro

陈帅 6 年之前
父節點
當前提交
d61911af99

+ 0 - 9
config/config.ts

@@ -54,15 +54,6 @@ const plugins: IPlugin[] = [
       autoAddMenu: true,
     },
   ],
-  // ...(!process.env.TEST && os.platform() === 'darwin'
-  //   ? {
-  //       dll: {
-  //         include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
-  //         exclude: ['@babel/runtime'],
-  //       },
-  //       hardSource: true,
-  //     }
-  //   : {}),
 ];
 
 // 针对 preview.pro.ant.design 的 GA 统计代码

+ 1 - 1
package.json

@@ -56,7 +56,7 @@
     "not ie <= 10"
   ],
   "dependencies": {
-    "@ant-design/pro-layout": "^4.0.3",
+    "@ant-design/pro-layout": "^4.1.0",
     "@antv/data-set": "^0.10.1",
     "antd": "^3.16.1",
     "bizcharts": "^3.4.3",

+ 29 - 0
src/components/Authorized/Authorized.tsx

@@ -0,0 +1,29 @@
+import CheckPermissions from './CheckPermissions';
+import { IAuthorityType } from './CheckPermissions';
+import Secured from './Secured';
+import check from './CheckPermissions';
+import AuthorizedRoute from './AuthorizedRoute';
+import React from 'react';
+
+interface IAuthorizedProps {
+  authority: IAuthorityType;
+  noMatch?: React.ReactNode;
+}
+
+type IAuthorizedType = React.FunctionComponent<IAuthorizedProps> & {
+  Secured: typeof Secured;
+  check: typeof check;
+  AuthorizedRoute: typeof AuthorizedRoute;
+};
+
+const Authorized: React.FunctionComponent<IAuthorizedProps> = ({
+  children,
+  authority,
+  noMatch = null,
+}) => {
+  const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
+  const dom = CheckPermissions(authority, childrenRender, noMatch);
+  return <>{dom}</>;
+};
+
+export default Authorized as IAuthorizedType;

+ 32 - 0
src/components/Authorized/AuthorizedRoute.tsx

@@ -0,0 +1,32 @@
+import React from 'react';
+import { Route, Redirect } from 'umi';
+import Authorized from './Authorized';
+import { IAuthorityType } from './CheckPermissions';
+
+interface IAuthorizedRoutePops {
+  currentAuthority: string;
+  component: React.ComponentClass<any, any>;
+  render: () => React.ReactNode;
+  redirectPath: string;
+  authority: IAuthorityType;
+}
+
+const AuthorizedRoute: React.SFC<IAuthorizedRoutePops> = ({
+  component: Component,
+  render,
+  authority,
+  redirectPath,
+  ...rest
+}) => (
+  <Authorized
+    authority={authority}
+    noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
+  >
+    <Route
+      {...rest}
+      render={(props: any) => (Component ? <Component {...props} /> : render(props))}
+    />
+  </Authorized>
+);
+
+export default AuthorizedRoute;

+ 55 - 0
src/components/Authorized/CheckPermissions.test.js

@@ -0,0 +1,55 @@
+import { checkPermissions } from './CheckPermissions';
+
+const target = 'ok';
+const error = 'error';
+
+describe('test CheckPermissions', () => {
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
+  });
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
+  });
+  it('authority is undefined , return ok', () => {
+    expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
+  });
+  it('currentAuthority is undefined , return error', () => {
+    expect(checkPermissions('admin', null, target, error)).toEqual('error');
+  });
+  it('Wrong string permission authentication', () => {
+    expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
+  });
+  it('Correct Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
+  });
+  it('Wrong Array permission authentication,currentAuthority error', () => {
+    expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
+  });
+  it('Wrong Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
+  });
+  it('Wrong Function permission authentication', () => {
+    expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
+  });
+  it('Correct Function permission authentication', () => {
+    expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
+  });
+  it('authority is string, currentAuthority is array, return ok', () => {
+    expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
+  });
+  it('authority is string, currentAuthority is array, return ok', () => {
+    expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
+  });
+  it('authority is array, currentAuthority is array, return ok', () => {
+    expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
+  });
+  it('Wrong Function permission authentication', () => {
+    expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
+  });
+  it('Correct Function permission authentication', () => {
+    expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
+  });
+  it('authority is undefined , return ok', () => {
+    expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
+  });
+});

+ 84 - 0
src/components/Authorized/CheckPermissions.tsx

@@ -0,0 +1,84 @@
+import React from 'react';
+// eslint-disable-next-line import/no-cycle
+import PromiseRender from './PromiseRender';
+import { CURRENT } from './renderAuthorize';
+
+export type IAuthorityType =
+  | string
+  | string[]
+  | Promise<any>
+  | ((currentAuthority: string | string[]) => IAuthorityType);
+
+/**
+ * 通用权限检查方法
+ * Common check permissions method
+ * @param { 权限判定 | Permission judgment } authority
+ * @param { 你的权限 | Your permission description } currentAuthority
+ * @param { 通过的组件 | Passing components } target
+ * @param { 未通过的组件 | no pass components } Exception
+ */
+const checkPermissions = (
+  authority: IAuthorityType,
+  currentAuthority: string | string[],
+  target: React.ComponentClass<any, any> | React.ReactNode,
+  Exception: React.ReactNode,
+): React.ReactNode => {
+  // 没有判定权限.默认查看所有
+  // Retirement authority, return target;
+  if (!authority) {
+    return target;
+  }
+  // 数组处理
+  if (Array.isArray(authority)) {
+    if (Array.isArray(currentAuthority)) {
+      if (currentAuthority.some(item => authority.includes(item))) {
+        return target;
+      }
+    } else if (authority.includes(currentAuthority)) {
+      return target;
+    }
+    return Exception;
+  }
+  // string 处理
+  if (typeof authority === 'string') {
+    if (Array.isArray(currentAuthority)) {
+      if (currentAuthority.some(item => authority === item)) {
+        return target;
+      }
+    } else if (authority === currentAuthority) {
+      return target;
+    }
+    return Exception;
+  }
+  // Promise 处理
+  if (authority instanceof Promise) {
+    return <PromiseRender ok={target} error={Exception} promise={authority} />;
+  }
+  // Function 处理
+  if (typeof authority === 'function') {
+    try {
+      const bool = authority(currentAuthority);
+      // 函数执行后返回值是 Promise
+      if (bool instanceof Promise) {
+        return <PromiseRender ok={target} error={Exception} promise={bool} />;
+      }
+      if (bool) {
+        return target;
+      }
+      return Exception;
+    } catch (error) {
+      throw error;
+    }
+  }
+  throw new Error('unsupported parameters');
+};
+
+export { checkPermissions };
+
+const check = (
+  authority: IAuthorityType,
+  target: React.ComponentClass<any, any> | React.ReactNode,
+  Exception: React.ReactNode,
+): React.ReactNode => checkPermissions(authority, CURRENT, target, Exception);
+
+export default check;

+ 91 - 0
src/components/Authorized/PromiseRender.tsx

@@ -0,0 +1,91 @@
+import { Spin } from 'antd';
+import isEqual from 'lodash/isEqual';
+import React from 'react';
+// eslint-disable-next-line import/no-cycle
+import { isComponentClass } from './Secured';
+
+interface IPromiseRenderProps {
+  ok: React.ReactNode;
+  error: React.ReactNode;
+  promise: Promise<any>;
+}
+
+interface IPromiseRenderState {
+  component: React.ComponentClass<any, any> | React.FunctionComponent<any>;
+}
+
+export default class PromiseRender extends React.Component<
+  IPromiseRenderProps,
+  IPromiseRenderState
+> {
+  state: IPromiseRenderState = {
+    component: () => null,
+  };
+
+  componentDidMount() {
+    this.setRenderComponent(this.props);
+  }
+
+  shouldComponentUpdate = (nextProps: IPromiseRenderProps, nextState: IPromiseRenderState) => {
+    const { component } = this.state;
+    if (!isEqual(nextProps, this.props)) {
+      this.setRenderComponent(nextProps);
+    }
+    if (nextState.component !== component) return true;
+    return false;
+  };
+
+  // set render Component : ok or error
+  setRenderComponent(props: IPromiseRenderProps) {
+    const ok = this.checkIsInstantiation(props.ok);
+    const error = this.checkIsInstantiation(props.error);
+    props.promise
+      .then(() => {
+        this.setState({
+          component: ok,
+        });
+      })
+      .catch(() => {
+        this.setState({
+          component: error,
+        });
+      });
+  }
+
+  // Determine whether the incoming component has been instantiated
+  // AuthorizedRoute is already instantiated
+  // Authorized  render is already instantiated, children is no instantiated
+  // Secured is not instantiated
+  checkIsInstantiation = (
+    target: React.ReactNode | React.ComponentClass<any, any>,
+  ): React.FunctionComponent<any> => {
+    if (isComponentClass(target)) {
+      const Target = target as React.ComponentClass<any, any>;
+      return (props: any) => <Target {...props} />;
+    }
+    if (React.isValidElement(target)) {
+      return (props: any) => React.cloneElement(target, props);
+    }
+    return () => target as (React.ReactNode & null);
+  };
+
+  render() {
+    const { component: Component } = this.state;
+    const { ok, error, promise, ...rest } = this.props;
+    return Component ? (
+      <Component {...rest} />
+    ) : (
+      <div
+        style={{
+          width: '100%',
+          height: '100%',
+          margin: 'auto',
+          paddingTop: 50,
+          textAlign: 'center',
+        }}
+      >
+        <Spin size="large" />
+      </div>
+    );
+  }
+}

+ 68 - 0
src/components/Authorized/Secured.tsx

@@ -0,0 +1,68 @@
+import React from 'react';
+import CheckPermissions from './CheckPermissions';
+
+/**
+ * 默认不能访问任何页面
+ * default is "NULL"
+ */
+const Exception403 = () => 403;
+
+export const isComponentClass = (
+  component: React.ComponentClass<any, any> | React.ReactNode,
+): boolean => {
+  if (!component) return false;
+  const proto = Object.getPrototypeOf(component);
+  if (proto === React.Component || proto === Function.prototype) return true;
+  return isComponentClass(proto);
+};
+
+// Determine whether the incoming component has been instantiated
+// AuthorizedRoute is already instantiated
+// Authorized  render is already instantiated, children is no instantiated
+// Secured is not instantiated
+const checkIsInstantiation = (target: React.ComponentClass<any, any> | React.ReactNode) => {
+  if (isComponentClass(target)) {
+    const Target = target as React.ComponentClass<any, any>;
+    return (props: any) => <Target {...props} />;
+  }
+  if (React.isValidElement(target)) {
+    return (props: any) => React.cloneElement(target, props);
+  }
+  return () => target;
+};
+
+/**
+ * 用于判断是否拥有权限访问此 view 权限
+ * authority 支持传入 string, () => boolean | Promise
+ * e.g. 'user' 只有 user 用户能访问
+ * e.g. 'user,admin' user 和 admin 都能访问
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
+ * e.g. Promise  then 能访问   catch不能访问
+ * e.g. authority support incoming string, () => boolean | Promise
+ * e.g. 'user' only user user can access
+ * e.g. 'user, admin' user and admin can access
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
+ * e.g. Promise then can not access the visit to catch
+ * @param {string | function | Promise} authority
+ * @param {ReactNode} error 非必需参数
+ */
+const authorize = (authority: string, error?: React.ReactNode) => {
+  /**
+   * conversion into a class
+   * 防止传入字符串时找不到staticContext造成报错
+   * String parameters can cause staticContext not found error
+   */
+  let classError: boolean | React.FunctionComponent<any> = false;
+  if (error) {
+    classError = (() => error) as React.FunctionComponent<any>;
+  }
+  if (!authority) {
+    throw new Error('authority is required');
+  }
+  return function decideAuthority(target: React.ComponentClass<any, any> | React.ReactNode) {
+    const component = CheckPermissions(authority, target, classError || Exception403);
+    return checkIsInstantiation(component);
+  };
+};
+
+export default authorize;

+ 12 - 0
src/components/Authorized/index.tsx

@@ -0,0 +1,12 @@
+import Authorized from './Authorized';
+import AuthorizedRoute from './AuthorizedRoute';
+import Secured from './Secured';
+import check from './CheckPermissions';
+import renderAuthorize from './renderAuthorize';
+
+Authorized.Secured = Secured;
+Authorized.AuthorizedRoute = AuthorizedRoute;
+Authorized.check = check;
+
+const RenderAuthorize = renderAuthorize(Authorized);
+export default RenderAuthorize;

+ 27 - 0
src/components/Authorized/renderAuthorize.ts

@@ -0,0 +1,27 @@
+/* eslint-disable import/no-mutable-exports */
+let CURRENT: string | string[] = 'NULL';
+/**
+ * use  authority or getAuthority
+ * @param {string|()=>String} currentAuthority
+ */
+const renderAuthorize = (Authorized: any) => (
+  currentAuthority: string | string[] | (() => typeof CURRENT),
+) => {
+  if (currentAuthority) {
+    if (typeof currentAuthority === 'function') {
+      CURRENT = currentAuthority();
+    }
+    if (
+      Object.prototype.toString.call(currentAuthority) === '[object String]' ||
+      Array.isArray(currentAuthority)
+    ) {
+      CURRENT = currentAuthority as string[];
+    }
+  } else {
+    CURRENT = 'NULL';
+  }
+  return Authorized;
+};
+
+export { CURRENT };
+export default (Authorized: any) => renderAuthorize(Authorized);

+ 20 - 15
src/layouts/BasicLayout.tsx

@@ -10,6 +10,7 @@ import {
   BasicLayoutProps as BasicLayoutComponentsProps,
   MenuDataItem,
   Settings,
+  SettingDrawer,
 } from '@ant-design/pro-layout';
 
 export interface BasicLayoutProps extends BasicLayoutComponentsProps, ConnectProps {
@@ -39,21 +40,25 @@ const BasicLayout: React.FC<BasicLayoutProps> = props => {
     dispatch!({ type: 'global/changeLayoutCollapsed', payload });
 
   return (
-    <BasicLayoutComponents
-      formatMessage={formatMessage}
-      logo={logo}
-      onChangeSetting={settings =>
-        dispatch!({
-          type: 'settings/changeSetting',
-          payload: settings,
-        })
-      }
-      onChangeLayoutCollapsed={handleMenuCollapse}
-      renderRightContent={RightProps => <RightContent {...RightProps} />}
-      {...props}
-    >
-      {children}
-    </BasicLayoutComponents>
+    <>
+      <BasicLayoutComponents
+        logo={logo}
+        onCollapse={handleMenuCollapse}
+        rightContentRender={RightProps => <RightContent {...RightProps} />}
+        {...props}
+      >
+        {children}
+      </BasicLayoutComponents>
+      <SettingDrawer
+        settings={props.settings}
+        onSettingChange={settings =>
+          dispatch!({
+            type: 'settings/changeSetting',
+            payload: settings,
+          })
+        }
+      />
+    </>
   );
 };
 

+ 1 - 1
src/utils/Authorized.ts

@@ -1,4 +1,4 @@
-import { Authorized as RenderAuthorized } from 'ant-design-pro';
+import { default as RenderAuthorized } from '@/components/Authorized';
 import { getAuthority } from './authority';
 
 let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line