• 【招聘App】—— React/Nodejs/MongoDB全栈项目:信息完善&用户列表


    前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅。最终成果github地址:https://github.com/66Web/react-antd-zhaoping,欢迎star。


     一、信息完善

        boss信息完善页前端实现

    • container目录下:创建bossinfo组件目录,使用actd-mobile实现信息输入列表
      import React from 'react'
      import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile'
      import AvatarSelector from '../../component/avatar-selector/avatar-selector'
      
      class BossInfo extends React.Component{
          constructor(props){
              super(props)
              this.state = {
                  title: ''
              }
          }
          onChange(key, val){
              this.setState({
                  [key]: val
              }) 
          }
          render(){
              return (
                  <div>
                     <NavBar mode="dark">Boss完善信息页面</NavBar>
                      <AvatarSelector
                         selectAvatar={(imgname) => {
                             this.setState({
                                 avatar: imgname
                             })
                         }}
                      ></AvatarSelector>
                      <InputItem onChange={(v) => this.onChange('title', v)}>
                          招聘职位
                      </InputItem>
                      <InputItem onChange={(v) => this.onChange('company', v)}>
                          公司名称
                      </InputItem>
                      <InputItem onChange={(v) => this.onChange('money', v)}>
                          职位薪资
                      </InputItem>
                      <TextareaItem
                          rows={3}
                          title="职位要求"
                          autoHeight
                          onChange={(v) => this.onChange('desc', v)}
                      >
                      </TextareaItem>
                      <Button type="primary">保存</Button>
                  </div>
              )
          }
      }
      
      export default BossInfo
    • component目录下:创建avatar-selector选择头像组件目录

      import React from 'react'
      import {Grid, List} from 'antd-mobile'
      
      class AvatarSelector extends React.Component{
          constructor(props){
              super(props)
              this.state = {}
          }
          render(){
              const avatarList = 'boy,girl,man,woman,bull,chick,crab,hedgehog,hippopotamus,koala,lemur,pig,tiger,whale,zebra'
                                  .split(',')
                                  .map(v=>({
                                      icon:require(`../img/${v}.png`),
                                      text:v
                                  }))
              const gridHeader = this.state.text 
                                  ? (<div>
                                      <span>已选择头像</span>
                                      <img style={{ 20}} src={this.state.icon}/>
                                  </div>) 
                                  :  <div>'请选择头像'</div>                   
              return (
                  <div>
                      <List renderHeader={() => gridHeader}>
                          <Grid 
                              data={avatarList} 
                              columnNum={5}
                              onClick={elm => {
                                  this.setState(elm)
                                  this.props.selectAvatar(elm.text)
                              }}
                          />
                      </List>
                  </div> 
              )
          }
      }
      
      export default AvatarSelector 

        boss信息完善页后端实现

    • user.redux.js中:添加authSuccess验证成功相关的reducer和action
    1. authSuccess同步action:代替registerSuccess和loginSuccess,它们执行的是相同操作
      //action type
      const AUTH_SUCCESS = 'AUTH_SUCCESS' //验证成功
      
      //reducer中替换
      case AUTH_SUCCESS:
              return {...state, msg:'', redirectTo:getRedirectPath(action.payload), ...action.payload} 
      
      //同步action
      function authSuccess(obj){
      	const {pwd,...data} = obj
      	return {type: AUTH_SUCCESS, payload:data}
      }
    2. 定义update异步action:完善信息页提交后调用服务端/user/update接口,执行更新操作
      export function update(data){
          return dispatch=>{
              axios.post('/user/update', data)
                 .then(res=>{
                      if (res.status==200&&res.data.code===0) {
                          dispatch(authSuccess(res.data.data))
                      }else{
                          dispatch(errorMsg(res.data.msg))
                      }
                 })
          }
      }
      
    • user.js中:判断cookie中是否存储了userid,若有则根据userid查找并更新用户信息文档findByIdAndUpdate

      //更新信息
      Router.post('/update', function(req, res){
          const userid = req.cookies.userid
          if(!userid){
              return json.dumps({code:1})
          }
          const body = req.body
          User.findByIdAndUpdate(userid, body, function(err, doc){
              const data = Object.assign({},{
                  user: doc.user,
                  type: doc.type,
              }, body)
              return res.json({code:0, data})
          })
      })
      
    • bossinfo.js中:使用connect连接组件和redux

    1. 点击保存按钮时,调用redux中的update异步action更新数据

      import {connect} from 'react-redux'
      import {update} from '../../redux/user.redux'
      import {Redirect} from 'react-router-dom'
      
      @connect(
          state => state.user,
          {update}
      )
      
      <Button
           onClick={()=>{
                  this.props.update(this.state)
           }}
           type="primary"
      >保存</Button>  
    2. 判断props中redirect是否存在,且不等于当前路由即(location.pathname)时执行跳转

      const path = this.props.location.pathname
      const redirect = this.props.redirectTo
              return (
                  <div>
                     {redirect&&redirect!==path  ? <Redirect to={this.props.redirectTo}/> : null}

        牛人信息完善  

    • geniusinfo.js中:同bossinfo.js共用一套逻辑
      import React from 'react'
      import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile'
      import AvatarSelector from '../../component/avatar-selector/avatar-selector'
      import {connect} from 'react-redux'
      import {update} from '../../redux/user.redux'
      import {Redirect} from 'react-router-dom'
      
      @connect(
          state => state.user,
          {update}
      )
      class GeniusInfo extends React.Component{
          constructor(props){
              super(props)
              this.state = {
                  title: '',
                  desc: ''
              }
          }
          onChange(key, val){
              this.setState({
                  [key]: val
              }) 
          }
          render(){
              const path = this.props.location.pathname
              const redirect = this.props.redirectTo
              return (
                  <div>
                     {redirect&&redirect!==path  ? <Redirect to={this.props.redirectTo}/> : null}
                     <NavBar mode="dark">牛人完善信息页面</NavBar>
                      <AvatarSelector
                         selectAvatar={(imgname) => {
                             this.setState({
                                 avatar: imgname
                             })
                         }}
                      ></AvatarSelector>
                      <InputItem onChange={(v) => this.onChange('title', v)}>
                          求职职位
                      </InputItem>
                      <TextareaItem
                          rows={3}
                          title="个人简介"
                          autoHeight
                          onChange={(v) => this.onChange('desc', v)}
                      >
                      </TextareaItem>
                      <Button
                          onClick={()=>{
                              this.props.update(this.state)
                          }}
                          type="primary"
                      >保存</Button>
                  </div>
              )
          }
      }
      
      export default GeniusInfo
      

        组件属性类型校验

    • PropTypes提供了多种验证器:react15.5之前是封装在react中的,react16之后分离出来,需要另外安装
      npm install prop-types --save
    • JavaScript基础数据类型,包括数组、布尔、函数、数字、对象、字符串

    1. optionalArray: React.PropTypes.array,

    2. optionalBool: React.PropTypes.bool,

    3. optionalFunc: React.PropTypes.func,

    4. optionalNumber: React.PropTypes.number,

    5. optionalObject: React.PropTypes.object,

    6. optionalString: React.PropTypes.string

    • 如果不能为空isRequired

      avatar-selector.js中:指定组件属性类型为函数,且不能为空

      import PropTypes from 'prop-types'
      
      static propTypes = {
           selectAvatar: PropTypes.func.isRequired
      }
    • 指定数据类型成数组

      组件名称:PropTypes.arrayOf(PropTypes.number)  

    • 指定数据类型到对象

      组件名称:PropTypes.objectOf(PropTypes.number)  

      

    二、用户列表  

        应用骨架

    • index.js中:添加路由组件DashBoard
    1. 不设置path,则当路由匹配不到对应组件时,都会跳转到DashBoard组件中
    2. 使用DashBoard组件同时管理四个路由:/boss,/genius,/msg,/me
      import DashBoard from './component/dashboard/dashboard'
      
      <Route component={DashBoard}></Route>//一定要放在<Switch>内容的最下方  

        导航和跳转

    •  component目录下:创建dashboard管理组件
    1. 定义navList数组:根据底部导航条分类,集中存储路由必需信息对象
      const navList = [
                  {
                      path: '/boss',
                      text: '牛人',
                      icon: 'boss',
                      title: '牛人列表',
                      component: Boss,
                      hide: user.type == 'genius'
                  },
                  {
                      path: '/genius',
                      text: 'Boss',
                      icon: 'job',
                      title: 'Boss列表',
                      component: Genius,
                      hide: user.type == 'boss'
                  },
                  {
                      path: '/msg',
                      text: '消息',
                      icon: 'msg',
                      title: '消息列表',
                      component: Msg
                  },
                  {
                      path: '/me',
                      text: '我',
                      icon: 'user',
                      title: '个人中心',
                      component: User
                  }
              ]
    2. 使用NavBar组件:判断当前pathname与路由对象中的path相等时,找到对应的路由对象,显示顶部导航信息title

      import {NavBar} from 'antd-mobile'
      
      const {pathname} = this.props.location
      
      <NavBar mode='dard'>{navList.find(v => v.path==pathname).title}</NavBar>
    3. 应用NavLinkBar底部导航组件:将路由信息数组navList传入组件

      import NavLinkBar from '../navlink/navlink'
      
      <NavLinkBar data={navList}></NavLinkBar>
      

      注意:这里信息数组是必须要传的,因此需在navlink.js中添加组件属性类型校验为isRequired

      import PropTypes from 'prop-types'
      
      static propTypes = {
              data: PropTypes.array.isRequired
      }
      
    • component目录下:创建navlink.js底部导航组件 ↑ 

    1. 用户信息,聊天列表,职位列表页面共享底部TabBar
    2. 对获取到的navList先进行过滤:通过.filter过滤掉依据当前user.type指定要hide的路由对象
    3. 过滤后的navList通过遍历:为每一个要显示的路由对象设置TabBar.Item
      import React from 'react'
      import {TabBar} from 'antd-mobile'
      import PropTypes from 'prop-types'
      import {withRouter} from 'react-router-dom'
      
      @withRouter
      class NavLinkBar extends React.Component{
          static propTypes = {
              data: PropTypes.array.isRequired
          }
          render(){
             const {pathname} = this.props.location
             const navList = this.props.data.filter(v=>!v.hide)
             // console.log(navList)
             return (
                 <TabBar>
                     {navList.map(v=>(
                         <TabBar.Item
                             key={v.path}
                             title={v.text}
                             icon={{uri: require(`./img/${v.icon}.png`)}}
                             selectedIcon={{uri: require(`./img/${v.icon}-active.png`)}}
                             selected={pathname===v.path}
                             onPress={()=>{
                                 this.props.history.push(v.path)
                             }}
                         >
                         </TabBar.Item>
                     ))}
                 </TabBar>
             )
          }
      }
      
      export default NavLinkBar
      

        牛人列表 

    • Boss用户在/bose中看到的是牛人列表
    • dashboard.js中:通过Switch组件将遍历navList得到且匹配到的第一个路由对象信息注入<Route/>,实现导航对应的不同页面内容
      <div style={{marginTop: 45}}>
             <Switch>
                   {navList.map(v => (
                         <Route key={v.path} path={v.path} component={v.component}></Route>
                   ))}
             </Switch>
      </div>
      
    • boss.js中:调用axios.get('/user/list?type=genius'),获得所有牛人用户的信息

      componentDidMount(){
              axios.get('/user/list?type=genius')
                 .then(res => {
                    if(res.data.code==0){
                       this.setState({data:res.data.data})
                    }
                 })
      }  
    1. 遍历所有牛人用户的信息对象,使用Card、Card.Header、Card.Body显示牛人信息
    2. 判断牛人有无头像:如没有则默认没有完善用户信息,不显示在牛人列表中

      render(){
              // console.log(this.state.data)
              const Header = Card.Header
              const Body = Card.Body
              return (
                  <WingBlank>
                      <WhiteSpace />
                      {this.state.data.map(v=>(
                          v.avatar ? <Card key={v._id}>
                              <Header
                                 title={v.user}
                                 thumb={require(`../img/${v.avatar}.png`)}
                                 extra={<span>{v.title}</span>}
                              >
                              </Header>
                              <Body>
                                 {v.desc.split('
      ').map(item=>(
                                     <div key={item}>{item}</div>
                                 ))}
                              </Body>
                          </Card> : null
                      ))}
                  </WingBlank>
              )
      }
      
    • user.js中:修改/user/list的get请求,获取req.query中的type参数,依据type查询User中的对应类型的数据

      //用户信息列表
      Router.get('/list', function(req, res){
          const {type} = req.query
          // User.remove({}, function(err, doc){})
          User.find({type}, function(err, doc){
              return res.json({code:0, data:doc})
          })
      })  
    1. Router.get的参数:用res.query获取

    2. Router.post的参数:用res.body获取

        使用redux管理牛人列表  

    • redux目录下:创建chatuser.redux.js用户聊天相关redux数据管理
      import axios from 'axios'
      //action type
      const USER_LIST = 'USER_LIST'
      
      const initState = {
      	userlist:[]
      }
      
      //reducer
      export function chatuser(state=initState, action){
      	switch(action.type){
      		case USER_LIST:
      			return {...state, userlist:action.payload}
      		default:
      			return state
      	}
      }
      
      //action creator
      function userList(data){
      	return { type:USER_LIST, payload:data}
      }
      
      //异步操作数据的方法 
      export function getUserList(type){
      	return dispatch=>{
                  axios.get('/user/list?type='+type)
      		.then(res=>{
      		     if (res.data.code==0) {
                               //dispatch触发数据变化,执行action
      			 dispatch(userList(res.data.data))
      		     }
      		})
      	}
      }
    • reducer.js中:将chatuser合并进reducer,返回

      import { chatuser } from './redux/chatuser.redux'
      
      export default combineReducers({user, chatuser})
    • boss.js中:不再需要调用axios获取数据,而是使用connect连接组件和redux,并使用getUserList方法异步获取数据

      import {connect} from 'react-redux'
      import {getUserList} from '../../redux/chatuser.redux'
      
      @connect(
          state => state.chatuser,
          {getUserList}
      )
      
      componentDidMount(){
           this.props.getUserList('genius')
           // axios.get('/user/list?type=genius')
           //    .then(res => {
           //       if(res.data.code==0){
           //          this.setState({data:res.data.data})
           //       }
           //    })
      }
      
      //遍历userlist获得牛人信息对象
      {this.props.userlist.map(v=>(
      

        优化组件  

    • Boss列表组件和牛人列表组件的实现逻辑相同,会有大部分的复用代码,现将其抽离为一个基础组件
    • UserCard.js中:通过Card相关组件显示获取到的用户信息 
    1. 设置userlist为必需传的数组属性类型   
    2. Boss列表有两个不同的选项,判断v.type若存在显示这两条信息
      import React from 'react'
      import PropTypes from 'prop-types'
      import {Card, WhiteSpace, WingBlank} from 'antd-mobile'
      
      class UserCard extends React.Component{
           static propTypes = {
               userlist: PropTypes.array.isRequired
           }  
           render(){
              const Header = Card.Header
              const Body = Card.Body
               return (
                    <WingBlank>
                      <WhiteSpace />
                      {this.props.userlist.map(v=>(
                          v.avatar ? <Card key={v._id}>
                              <Header
                                 title={v.user}
                                 thumb={require(`../img/${v.avatar}.png`)}
                                 extra={<span>{v.title}</span>}
                              >
                              </Header>
                              <Body>
                                 {v.type=='boss' ? <div>公司:{v.company}</div> : null} 
                                 {v.desc.split('
      ').map(d=>(
                                     <div key={d}>{d}</div>
                                 ))}
                                 {v.type=='boss' ? <div>薪资:{v.money}</div> : null}
                              </Body>
                          </Card> : null
                      ))}
                  </WingBlank>
               )
           }
      }
      
      export default UserCard
      
    • boss.jsgenius.js中:删除重复代码,直接返回UserCard组件,传入redux中的userlist即可

      return <UserCard userlist={this.props.userlist}></UserCard>

    注:项目来自慕课网  

  • 相关阅读:
    1
    可测试性
    爬取信件信息(更新)
    爬虫爬取疫情数据存到文件
    python分析三国演义中出现次数最多的词作词频统计
    实验三
    scala统计学生成绩
    对于OpenCV的访问Mat元素的探究
    OpenCV+VS2017+Nivdia(待更新)
    Window10 CUDA和cunn安装
  • 原文地址:https://www.cnblogs.com/ljq66/p/10274005.html
Copyright © 2020-2023  润新知