umi文档

执行 npm create umi 创建 antd-pro 项目

路由

整个 umi 建立的项目可以自定义路由,也可以约定式路由,我就直接对自定义路由展开
配置可以写在 config/config.ts 下或者写在根目录下 .umirc.tsroutes

{
    path: '/',
    component: '../layouts/BlankLayout',
    routes: [
    {
        path: '/',
        component: '../layouts/UserLayout',
        Routes: ['src/pages/Authorized'],
        authority: ['admin', 'user'], // 权限角色
        routes: [
            {
                name: '/test',
                icon: 'smile',
                path: '/test',
                component: './test',
            },
            {
                name: '/test2',
                authority: ['admin', 'user'], // 权限角色
                routes: [
                    {
                        name: '',
                        icon: 'smile',
                        path: '/test2/step1'
                    }
                ]
            }

        ],
    }
}

侧边栏/导航栏

注意:我们暂时不对它内置路由到底是怎么实现的,但我想的其原理就是对react-router进行配置吧

路由设置完之后对导航栏进行配置, 文档

另外umi文档中的插件 也是可以进行导航栏的废纸的

@ant-design/pro-layout

文档

<ProLayout
    // 这里的route是在上面配置自动获取的,不配置那么就不会显示在导航栏中
    route={props.route}  
>
</ProLayout>

权限

说一下antd-pro的权限, 我们可以看到 utils/authority.ts, 作用是获取/设置localstorage中的用户角色,然后通过路由中的 Routeauthority 判断其是否有准入条件。

v4.1.0貌似有bug,不会经过pages/Authority.tsx判断权限

  • 先通过 pages/Authority.tsx 文件判断用户登录以及权限路由
    • 用户已登录, 获取当前location.pathname在路由表中的权限,一直向上找,跟localstorage获取的权限判断,没权限 跳转到 403
    • 用户没登录,跳转登录页
    • 然后再经过路由下的 Component, 或者说是他的Children

查看封装的 组件

项目多次使用该组件, 使用方法如下:

const notMatch = (
    <div>not Match</div>
)
/**
 * @param {string | string[] | Promise<boolean>} authority
 * @param { React.ReactNode } notMatch
 * 
*/

<Authorized authority={authorized!.authority} noMatch={noMatch}>
    {children}
</Authorized>

Authorized 实现

// 此时的 RenderAuthorize 来自 src/Authorithy/index
// 重新包装了一下Authority,使得 currentAuthority = ['admin', 'user'] 在整个周期都能使用
// getAuthority 就是获取当前权限
let Authorized = RenderAuthorize(getAuthority()); // 此时还是从新包装了一下Authorited
  • RenderAuthorize
const RenderAuthorize = renderAuthorize(Authorized);

返回闭包,同时导出CURRENT, 当前current 也就是 getAuthority()的值, 也可以理解成一个HOC

/**
 * use  authority or getAuthority
 * @param {string|()=>String} currentAuthority
 */
function renderAuthorize(Authorized) {
    return function (currentAuthority) {
        if (currentAuthority) {
            if (typeof currentAuthority === 'function') {
                CURRENT = currentAuthority();
            }

            if (Object.prototype.toString.call(currentAuthority) === '[object String]' || Array.isArray(currentAuthority))
                {
                    CURRENT = currentAuthority;
                }
            } else {
                CURRENT = 'NULL';
            }

        return Authorized;
    };
}
export { CURRENT }
export default (Authorized) => renderAuthorize(Authorized);

这个组件逻辑很简单, 需要看check函数,传入的authority 是 [‘admin’] 为例子
如果通过check 那么返回children, 否则返回 <notMatch>

const Authorized: React.FunctionComponent<AuthorizedProps> = ({
  children,
  authority,
  noMatch = (
    <Result
      status="403"
      title="403"
      subTitle="Sorry, you are not authorized to access this page."
    />
  ),
}) => {
  const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
  //
  const dom = check(authority, childrenRender, noMatch);
  return <>{dom}</>;
};
  • 在来看看check函数

下面的CURRENT 就是上面的renderAuthorize 导出的CURRENT

/**
 * @param {string| stirng[]} authority 这里的authority 就是 ['admin'], 也就是准入条件
 * @param { React.ReactNode } target 也就是 children
 * @param { React.ReactNode } Exception notMatch
*/
function check<T, K>(authority: IAuthorityType, target: T, Exception: K): T | K | React.ReactNode {
  return checkPermissions<T, K>(authority, CURRENT, target, Exception);
}
  • 再来看看 checkPermissions
  1. 如果准入权限为空,那么直接渲染target
  2. 要是authoritycurrentAuthority都是数组的时候,那看看有没有交集有交集那就返回target
  3. 要是authority为字符串,那么看看我们的current中有没有这个权限就可以了
  4. 要是authoritypromise那么就进入promise的操作
  5. 要是authority为方法,那就执行一下, 参数为current
/**
 * 通用权限检查方法
 * Common check permissions method
 * @param { 权限判定 | Permission judgment } authority
 * @param { 你的权限 | Your permission description } currentAuthority
 * @param { 通过的组件 | Passing components } target
 * @param { 未通过的组件 | no pass components } Exception
 */
const checkPermissions = <T, K>(
  authority: IAuthorityType,
  currentAuthority: string | string[],
  target: T,
  Exception: K,
): T | K | 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<T, K> ok={target} error={Exception} promise={authority} />;
  }
  // Function 处理
  if (typeof authority === 'function') {
    const bool = authority(currentAuthority);
    // 函数执行后返回值是 Promise
    if (bool instanceof Promise) {
      return <PromiseRender<T, K> ok={target} error={Exception} promise={bool} />;
    }
    if (bool) {
      return target;
    }
    return Exception;
  }
  throw new Error('unsupported parameters');
};

登录组件

pages/user/login 查看 index.tsx

Login页面有一个 包裹着两个 <Tab>, <Tab>下面又是一下Input组件,只是被重新包装了

// 我们可以看出 UserName, Password 等等都是LoginForm导出的
const { Tab, UserName, Password, Mobile, Captcha, Submit } = LoginForm;

我们看下 Login 这个页面的大体结构

  • 用一个<LoginForm /> 包裹着两个<Tab>, <Tab> 底下是登录按钮还有其他的登录方式
  • 其中一个<Tab>对应的是账号密码登录, 另一个是短信验证码登录
  • 下面的status, loginTyperedux的状态,表示登录失败后会有报错信息
  • <LoginForm>activeKey 只有两个 account, mobile 代表账号登录跟短信登录
<LoginForm activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
  <Tab key="account" tab="账户密码登录">
    {status === 'error' && loginType === 'account' && !submitting && (
      <LoginMessage content="账户或密码错误(admin/ant.design)" />
    )}
    <UserName/>
    <Password/>
  </Tab>
  <Tab key="mobile" tab="手机号登录">
    {status === 'error' && loginType === 'mobile' && !submitting && (
      <LoginMessage content="验证码错误" />
    )}
    <Mobile/>
    <Captcha/>
  </Tab>
  <div>
    <Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
      自动登录
    </Checkbox>
    <a>忘记密码</a>
  </div>
  <Submit loading={submitting}>登录</Submit>
  <div className={styles.other}>
    其他登录方式
    <AlipayCircleOutlined className={styles.icon} />
    <TaobaoCircleOutlined className={styles.icon} />
    <WeiboCircleOutlined className={styles.icon} />
    <Link className={styles.register} to="/user/register">
      注册账户
    </Link>
  </div>
</LoginForm>

  • 首先先创建一个 Context, 这个context 主要是给 <LoginTab> 使用的

LoginTab 实现

简单看就是

// antd 官网
<Tabs defaultActiveKey="1" onChange={callback}>
  <TabPane tab="Tab 1" key="1">
    Content of Tab Pane 1
  </TabPane>
  <TabPane tab="Tab 2" key="2">
    Content of Tab Pane 2
  </TabPane>
  <TabPane tab="Tab 3" key="3">
    Content of Tab Pane 3
  </TabPane>
</Tabs>
const LoginTab: React.FC<LoginTabProps> = (props) => {
  useEffect(() => {
    const uniqueId = generateId('login-tab-'); //治理生成唯一的tab id
    const { tabUtil } = props;
    if (tabUtil) {
      tabUtil.addTab(uniqueId);
    }
  }, []);
  const { children } = props;
  return <TabPane {...props}>{props.active && children}</TabPane>;
};

const WrapContext: React.FC<TabPaneProps> & {
  typeName: string;
} = (props) => (
  <LoginContext.Consumer>
    {(value) => <LoginTab tabUtil={value.tabUtil} {...props} />}
  </LoginContext.Consumer>
);

我们看看provider 的用法

// 此时这里用上了hook然后加载的时候会加载一个tab,当切换的时候会添加另一个tab
const [tabs, setTabs] = useState<string[]>([]);
// 这个active貌似没什么用
const [active, setActive] = useState({});
// 此时声明当前的tab是哪一个 是 account 还是 mobile
const [type, setType] = useMergeValue('', {
  value: props.activeKey,
  onChange: props.onTabChange,
});
<LoginContext.Provider
  value={{
    tabUtil: {
      addTab: (id) => {
        setTabs([...tabs, id]);
      },
      removeTab: (id) => {
        setTabs(tabs.filter((currentId) => currentId !== id));
      },
    },
    updateActive: (activeItem) => {
      if (!active) return;
      if (active[type]) {
        active[type].push(activeItem);
      } else {
        active[type] = [activeItem];
      }
      setActive(active);
    },
  }}
>
<LoginContext.Provider>