React project actual combat renting app project (10) personal center module & login access control & project packaging and deployment online

foreword

1. Personal center module

1.1 Rendering diagram of personal center module

1.2 Basic layout of personal center module

Add the following code in src/pages/Profile/index.js:

render() {
    return (
      <div className={styles.root}>
        {/* 个人信息 */}
        <div className={styles.title}>
          <img
            className={styles.bg}
            src={BASE_URL + '/img/profile/bg.png'}
            alt="背景图"
          />
          <div className={styles.info}>
            <div className={styles.myIcon}>
              <img
                className={styles.avatar}
                src={avatar || DEFAULT_AVATAR}
                alt="icon"
              />
            </div>
            <div className={styles.user}>
              <div className={styles.name}>{nickname || '游客'}</div>
              {/* 登录后展示: */}
              {isLogin ? (
                <>
                  <div className={styles.auth}>
                    <span onClick={this.logout}>退出</span>
                  </div>
                  <div className={styles.edit}>
                    编辑个人资料
                    <span className={styles.arrow}>
                      <i className="iconfont icon-arrow" />
                    </span>
                  </div>
                </>
              ) : (
                <div className={styles.edit}>
                  <Button
                    type="primary"
                    size="small"
                    inline
                    onClick={() => history.push('/login')}
                  >
                    去登录
                  </Button>
                </div>
              )}
              {/* 未登录展示: */}
            </div>
          </div>
        </div>
        {/* 九宫格菜单 */}
        <Grid
          data={menus}
          // 列数
          columnNum={3}
          // 不需要分割线
          hasLine={false}
          // 渲染每一项
          renderItem={item =>
            item.to ? (
              <Link to={item.to}>
                <div className={styles.menuItem}>
                  <i className={`iconfont ${item.iconfont}`} />
                  <span>{item.name}</span>
                </div>
              </Link>
            ) : (
              <div className={styles.menuItem}>
                <i className={`iconfont ${item.iconfont}`} />
                <span>{item.name}</span>
              </div>
            )
          }
        />
        {/* 加入我们 */}
        <div className={styles.ad}>
          <img src={BASE_URL + '/img/profile/join.png'} alt="" />
        </div>
      </div>
    )
  }

1.2 Personal center module function realization

1.2.1 Determine whether the user is logged in

Implementation steps:

1、在state中添加两个状态:isLogin(是否登录)和userInfo(用户信息)
2、从utils中导入isAuth(登录状态)、getToken(获取token)
3、创建方法getUserInfo,用户来获取个人资料
4、在方法中,通过isLogin判断用户是否登录
5、如果没有登录,则不发送请求,渲染未登录信息
6、如果已登录,就根据接口发送请求,获取用户个人资料
7、渲染个人资料数据

Code example:
Add the following code in src/pages/Profile/index.js:

import { BASE_URL, isAuth, getToken, API } from '../../utils'

export default class Profile extends Component {
  state = {
    // 是否登录
    isLogin: isAuth(),
    // 用户信息
    userInfo: {
      avatar: '',
      nickname: ''
    }
  }

  // 注意:不要忘了在进入页面时调用方法 !
  componentDidMount() {
    this.getUserInfo()
  }

  async getUserInfo() {
    if (!this.state.isLogin) {
      // 未登录
      return
    }

    // 发送请求,获取个人资料
    const res = await API.get('/user', {
      headers: {
        authorization: getToken()
      }
    })

    if (res.data.status === 200) {
      const { avatar, nickname } = res.data.body
      this.setState({
        userInfo: {
          avatar: BASE_URL + avatar,
          nickname
        }
      })
    }
  }

  render() {
    const { history } = this.props

    const {
      isLogin,
      userInfo: { avatar, nickname }
    } = this.state

    return (
      <div className={styles.root}>
        {/* 个人信息 */}
        <div className={styles.title}>
          <img
            className={styles.bg}
            src={BASE_URL + '/img/profile/bg.png'}
            alt="背景图"
          />
          <div className={styles.info}>
            <div className={styles.myIcon}>
              <img
                className={styles.avatar}
                src={avatar || DEFAULT_AVATAR}
                alt="icon"
              />
            </div>
            <div className={styles.user}>
              <div className={styles.name}>{nickname || '游客'}</div>
              {/* 登录后展示: */}
              {isLogin ? (
                <>
                  <div className={styles.auth}>
                    <span onClick={this.logout}>退出</span>
                  </div>
                  <div className={styles.edit}>
                    编辑个人资料
                    <span className={styles.arrow}>
                      <i className="iconfont icon-arrow" />
                    </span>
                  </div>
                </>
              ) : (
                <div className={styles.edit}>
                  <Button
                    type="primary"
                    size="small"
                    inline
                    onClick={() => history.push('/login')}
                  >
                    去登录
                  </Button>
                </div>
              )}

              {/* 未登录展示: */}
            </div>
          </div>
        </div>
        {/* 九宫格菜单 */}
        <Grid
          data={menus}
          columnNum={3}
          hasLine={false}
          renderItem={item =>
            item.to ? (
              <Link to={item.to}>
                <div className={styles.menuItem}>
                  <i className={`iconfont ${item.iconfont}`} />
                  <span>{item.name}</span>
                </div>
              </Link>
            ) : (
              <div className={styles.menuItem}>
                <i className={`iconfont ${item.iconfont}`} />
                <span>{item.name}</span>
              </div>
            )
          }
        />


        {/* 加入我们 */}
        <div className={styles.ad}>
          <img src={BASE_URL + '/img/profile/join.png'} alt="" />
        </div>
      </div>
    )
  }
}

1.2.2 Exit function

Implementation steps:

1、点击退出按钮,弹出对话框,提示是否确定退出
2、给退出按钮绑定点击事件,创建方法logout作为事件处理函数
3、导入Modal对话框组件(弹出模态框)
4、在方法中,拷贝Modal组件文档中确认对话框的示例代码
5、修改对话框的文字提示
6、在退出按钮的事件处理程序中,先调用退出接口(让服务器端退出),再移除本地token(本地退出)
7、把登录状态isLogin设置为false
8、清空用户状态对象

Code example:
Add the following code in src/pages/Profile/index.js:

import {..., Modal } from 'antd-mobile'

const alert = Modal.alert 

 // 退出
  logout = () => {
    alert('提示', '是否确定退出?', [
      { text: '取消' },
      {
        text: '退出',
        onPress: async () => {
          // 调用退出接口
          await API.post('/user/logout', null, {
            headers: {
              authorization: getToken()
            }
          })

          // 移除本地token
          removeToken()

          // 处理状态
          this.setState({
            isLogin: false,
            userInfo: {
              avatar: '',
              nickname: ''
            }
          })
        }
      }
    ])
  }

2. Login access control

2.1 Use axios interceptor to process token uniformly

Implementation steps:

1、在api.js 中,添加请求拦截器 (API.interceptors.request.user())
2、获取到当前请求的接口路径(url)
3、判断接口路径,是否以/user 开头,并且不是登录或注册接口(只给需要的接口添加请求头)
4、如果是,就添加请求头Authorization
5、添加响应拦截器 (API.interceptors.response.use())
6、判断返回值中的状态码
7、如果是400,标示token超时或异常,直接移除token

Code example:
Add the following code in src/utils/api.js:

// 添加请求拦截器
API.interceptors.request.use(config => {
  const { url } = config
  // 判断请求url路径
  if (
    url.startsWith('/user') &&
    !url.startsWith('/user/login') &&
    !url.startsWith('/user/registered')
  ) {
    // 添加请求头
    config.headers.Authorization = getToken()
  }
  return config
})

// 添加响应拦截器
API.interceptors.response.use(response => {
  const { status } = response.data
  if (status === 400) {
    // 此时,说明 token 失效,直接移除 token 即可
    removeToken()
  }
  return response
})

2.2 Use AuthRoute authentication routing component to control page jump

Implementation principle:

1、限制某个页面只能在登陆的情况下访问,但是在React路由中并没有直接提供该组件,需要手动封装,来实现登陆访问控制(类似与Vue路由的导航守卫)
2、AuthRoute 组件实际上就是对原来Route组件做了一次包装,来实现一些额外的功能
3、render方法:render props模式,指定该路由要渲染的组件内容
4、Redirect组件:重定向组件,通过to属性,指定要跳转的路由信息

Implementation steps:

1、在components目录中创建AuthRoute/index.js 文件
2、创建组件AuthRoute并导出
3、在AuthRoute组件中返回Route组件(在Route基础上做了一层包装,用于实现自定义功能)
4、给Route组件,添加render方法,指定改组件要渲染的内容(类似与component属性)
5、在render方法中,调用isAuth() 判断是否登陆
6、如果登陆了,就渲染当前组件(通过参数component获取到要渲染的组件,需要重命名)
7、如果没有登陆,就重定向到登陆页面,并且指定登陆成功后要跳转的页面路径
8、将AuthRoute组件接收到的props原样传递给Route组件(保证与Route组件使用方式相同)
9、使用AuthRoute组件配置路由规则,验证是否实现页面的登陆访问控制

Code example:
Add the following code in src/components/AuthRoute/index.js:

const AuthRoute = ({ component: Component, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        const isLogin = isAuth()

        if (isLogin) {
          // 已登录
          // 将 props 传递给组件,组件中才能获取到路由相关信息
          return <Component {...props} />
        } else {
          // 未登录
          return (
            <Redirect
              to={
   
   {
                pathname: '/login',
                state: {
                  from: props.location
                }
              }}
            />
          )
        }
      }}
    />
  )
}
export default AuthRoute

2.3 Modify login success jump

Implementation steps:

1、登陆成功后,判断是否需要跳转到用户想要访问的页面(判断props.location.state 是否有值)
2、如果不需要,则直接调用history.go(-1) 返回上一页
3、如果需要,就跳转到from.pathname 指定的页面(推荐使用replace方法模式,不是push)

Code example:
Add the following code in src/pages/Login/index.js:

// 表单的提交事件
  handleSubmit: async (values, { props }) => {
    ...
    if (status === 200) {
      // 登录成功
      localStorage.setItem('hkzf_token', body.token)
      if (!props.location.state) {
        // 此时,表示是直接进入到了该页面,直接调用 go(-1) 即可
        props.history.go(-1)
      } else {
        props.history.replace(props.location.state.from.pathname)
      }
    } else {
      // 登录失败
      Toast.info(description, 2, null, false)
    }
  }

3. Project packaging

Packaging steps:

1、在根目录创建 .env.production 文件,配置生产环境的接口基础路径
2、在项目根目录中,打开终端
3、输入命令: yarn build,进行项目打包,生成build文件夹(打包好的项目内容)
4、将build目录中的文件内容,部署到都服务器中即可
5、打包成功后,在根目录中就会生成一个build文件夹

Summarize

So far, our project has been fully developed. Due to the limited space, we only show the writing of a part of the business logic. In the real production environment, we must flexibly use this knowledge according to the company's needs.

Guess you like

Origin blog.csdn.net/qq_40652101/article/details/128966872