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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

前言:最近在学习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={{width: 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('\n').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('\n').map(d=>(
    <div key={d}>{d}</div>
    ))}
    {v.type=='boss' ? <div>薪资:{v.money}</div> : null}
    </Body>
    </Card> : null
    ))}
    </WingBlank>
    )
    }
    } export default UserCard
  • boss.js和genius.js中:删除重复代码,直接返回UserCard组件,传入redux中的userlist即可

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

注:项目来自慕课网  

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: goNodeJS