学习基于React-AntPro框架的二次开发-持续更新

1,070 阅读3分钟

一、项目起步

1、下载pro-cli脚手架并且创建项目:

npm i @ant-design/pro-cli -g
pro create myapp

2、安装依赖

cd myapp && npm install
cd myapp && yarn

3、启动项目

npm run start

此时项目将会启动在 http://localhost:8000

二、项目的基本样式修改

config/defaultSettings.ts中我们可以看到如下:

const settings: LayoutSettings & {
  pwa?: boolean;
  logo?: string;
} = {
  // 修改左上角的 logo
  logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
  // 设置标题的 title
  title: 'Ant Design Pro',
  navTheme: 'light',
  // 拂晓蓝
  primaryColor: '#1890ff',
  //导航布局方式
  layout: 'mix',
  //layout 的内容模式,Fluid:自适应,Fixed:定宽 1200px
  contentWidth: 'Fluid',
  fixSiderbar: true,
};

export default settings;

三、处理登录页

1、处理登录页基本样式及表单

在登录页E:\laixinyu\demo\mydemo\src\pages\User\Login\index.tsx中直接利用现成的表单进行修改:

  • LoginForm - 表单组件
  • ProFormText - 输入框组件
  • ProFormText.Password - 密码输入组件

需要注意的是ProFormText或者ProFormText.Password中的name属性将作为后面接收表单提交的值的属性名

2、提交表单数据并登录 我们可以在LoginForm中绑定onFinish属性,他将会在我们提交表单数据时执行对应的函数:

const submitdata = (val)=>{
    
   console.log(val)
   /**
   *这里的val是一个对象包含了我们在表单中输入的数据
   *{ username:admin, password:123 }
   *我们在这里接收到数据后 将会调用登录接口并且将数据传入
   */
}
<LoginForm
onFinish={submitdata}
>
<LoginForm/>

在登录之后,我们要通过useModel将token以及之后获取的用户信息存入在model中,并且需要将token做持久化处理

3、创建Model(简易数据流)

src下新建一个文件夹model,新建文件user.ts,并且内容如下:

import { useState, useCallback } from 'react';
import { setLocalToken, clearLocalToken } from '@/utils';

// 用户信息
export default () => {
  const [token, setToken] = useState('');
  const [userInfo, setUserInfo] = useState({});
  // 设置token
  const addToken = useCallback((token) => {
      setToken(token);
      setLocalToken(token);
  }, []);
  // 设置userInfo
  const addUserInfo = useCallback((userInfo) => setUserInfo(userInfo), []);
  return { token, userInfo, addToken, addUserInfo };
};

4、使用Model--useModel

注意:useModel('模块名'),这里的模块名为文件名 如:user.ts => useModel('user)

import { useModel } from '@umijs/max';
import React from 'react'

export Login:React.FC = ()=>{
const user = useModel('user')
//调用user中的setToken方法设置token
user.setToken(token)
//同时,也可以获取数据
const token = user.token //这里就可以获取到token的值
}

5、处理currentUser-访问权限

通过在initialState中设置currentUser使其通过路由鉴权

 const { initialState, setInitialState } = useModel('@@initialState');
 //在登录并且获得用户数据userInfo后 将用户数据存入currentUser
 setInitialState({
 ...initialState,
 //这里的access属性主要是为了鉴权使用
 currentUser:{...userInfo,access:userInfo.role[0]}
 })

除此之外,我们还需要在app.tsx中处理currentUser,这种情况主要是当用户直接访问/admin页的时候,就会去做鉴权,如果当前存在token并且它还未失效,那么就会被允许进入到/admin页面

 // 如果不是登录页面,执行
  if (window.location.pathname !== loginPath) {
  //当访问页面不是login页的时候 去鉴权
  //拉取用户信息 存入到initialState的currentUser中
    let currentUser = await fetchUserInfo();
    if (currentUser) {
      currentUser = { ...currentUser, access: currentUser.roles[0] };
    }
    const getRoutes = await getMenu();
    const setRoutes = getRoutes.map((item) => {
      return {
        name: item.title,
        icon: iconList[item.icon === 'sys-tools' ? 'sysTools' : item.icon],
        path: `/${item.path}`,
        routes: item.children && loopRoutes(item.children),
      };
    });
    return {
      fetchUserInfo,
      currentUser,
      routes: setRoutes,
      settings: defaultSettings,
    };
  }

这样一来 我们就成功登录,并且进入到/admin的页面

四、处理Admin-Layout页面的样式

在ProLayout中进行配置

// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
  return {
    token: {
    //顶部header样式
      header: {
        heightLayoutHeader: 60,
      },
      //侧边栏样式
      sider: {
        colorBgCollapsedButton: '#fff',
        colorTextCollapsedButtonHover: 'rgba(0,0,0,0.65)',
        colorTextCollapsedButton: 'rgba(0,0,0,0.45)',
        colorMenuBackground: '#195ffe',
        colorBgMenuItemCollapsedHover: 'rgba(0,0,0,0.06)',
        colorBgMenuItemCollapsedSelected: 'rgba(0,0,0,0.15)',
        colorMenuItemDivider: 'rgba(255,255,255,0.15)',
        colorBgMenuItemHover: 'rgba(0,0,0,0.06)',
        colorBgMenuItemSelected: 'rgba(0,0,0,0.15)',
        colorTextMenuSelected: '#fff',
        colorTextMenuItemHover: 'rgba(255,255,255,0.75)',
        colorTextMenu: 'rgba(255,255,255,1)',
        colorTextMenuSecondary: 'rgba(255,255,255,0.65)',
        colorTextMenuTitle: 'rgba(255,255,255,0.95)',
        colorTextMenuActive: 'rgba(255,255,255,0.95)',
        colorTextSubMenuSelected: '#fff',
      },
    },
    //侧边栏宽度
    siderWidth: 200,
    //右上侧内容 例如用户头像等
    rightContentRender: () => <RightContent />,
    waterMarkProps: {
      content: initialState?.currentUser?.name,
    },
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!initialState?.currentUser && location.pathname !== loginPath) {
        history.push(loginPath);
      }
    },
    layoutBgImgList: [
      {
        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
        left: 85,
        bottom: 100,
        height: '303px',
      },
      {
        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
        bottom: -68,
        right: -45,
        height: '303px',
      },
      {
        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
        bottom: 0,
        left: 0,
        width: '331px',
      },
    ],
    links: isDev ? [] : [],
    menuHeaderRender: undefined,
    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    // 增加一个 loading 的状态
    childrenRender: (children, props) => {
      // if (initialState?.loading) return <PageLoading />;
      return (
        <>
          {children}
          {!props.location?.pathname?.includes('/login') && (
            <SettingDrawer
              disableUrlParams
              enableDarkTheme
              settings={initialState?.settings}
              onSettingChange={(settings) => {
                setInitialState((preInitialState) => ({
                  ...preInitialState,
                  settings,
                }));
              }}
            />
          )}
        </>
      );
    },
    ...initialState?.settings,
  };
};

五、动态控制菜单

在这里我们主要通过ProLayout中的postMenuData属性或者menu来动态设置菜单(从服务器拉取菜单内容)

eg:

postMenuData:initialState?.routes
menu: { //推荐
      params: initialState,
      request: async () => {
        return initialState?.routes;
      },
    },

1、首先,我们在app.tsx中的getInitialState中定义初始化菜单数据routes


// 递归处理路由方法
const loopRoutes = (routes) => {
  const newRes = routes.map((item) => {
    return {
      name: item.title,
      icon: iconList[item.icon === 'sys-tools' ? 'sysTools' : item.icon],
      path: `/${item.path}`,
      routes: item.children && loopRoutes(item.children),
    };
  });
  return newRes;
};

export async function getInitialState(): Promise<{
  settings?: Partial<LayoutSettings>;
  currentUser?: API.CurrentUser;
  loading?: boolean;
  //菜单数组-定义 +
  routes?: MenuDataItem[];  // +
  fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}>{
...
...
//获取最新菜单
const getRoutes = await getroutes()
if(getRoutes){
//重组菜单数据
const setRoutes = getRoutes.map((item) => {
      return {
        name: item.title,
        icon: iconList[item.icon === 'sys-tools' ? 'sysTools' : item.icon],
        path: `/${item.path}`,
         //因为有些存在子菜单,所以进行递归处理
        routes: item.children && loopRoutes(item.children),
      };
    });
    //然后将获取到的菜单数据加入到routes中
    return {
    routes:setRoutes
    }
}
}

2、除此之外,我们还需要在登录成功后,获取路由。 在/Login/index.tsx中,在登陆成功后 调用获取菜单的API,然后通过setInitialState一并将用户数据和菜单数据加入其中:

// 登录
    try {
      const res = await userLogin(formData);
      // 持久化token
      token_model.addToken(res.token);
      // 加载用户信息添加权限
      const userInfo = await fetchUserInfo();
      const getRoutes = await addRoutes();
      message.success('登录成功');
      // 加载菜单
      setInitialState({
        ...initialState,
        //菜单数据
        routes: getRoutes,
        //用户信息
        currentUser: { ...userInfo, access: userInfo.roles[0] },
      });
    } catch (err) {
      message.error(err.response.data);
    }
    //跳转路由
    history.push('/admin');

六、退出登录

我们在src\components\RightContent\AvatarDropdown.tsx中,退出登录时需要清除本地token值以及initialState中的用户数据菜单数据

目前遇到的问题

1、在局部less中书写或覆盖ant组件样式,不生效:

解决: 在局部less文件中使用global

:global(.ant-tag){
background-color:red;
}