├── .babelrc ├── .gitignore ├── README.md ├── cmrh.conf.js ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts ├── build.js ├── start.js └── test.js ├── server ├── model.js ├── server.js └── user.js ├── src ├── app.js ├── componment │ ├── authroute │ │ └── authroute.js │ ├── avatar-selector │ │ └── avatar-selector.js │ ├── chat │ │ └── chat.js │ ├── consignor │ │ └── consignor.js │ ├── dashboard │ │ └── dashboard.js │ ├── genius │ │ └── genius.js │ ├── hoc-form │ │ └── hoc-form.js │ ├── img │ │ ├── 01.jpg │ │ ├── 02.jpg │ │ ├── 03.jpg │ │ ├── 04.jpg │ │ ├── 05.jpg │ │ ├── 06.jpg │ │ ├── 07.jpg │ │ └── 08.jpg │ ├── logo │ │ ├── logo.css │ │ ├── logo.jpg │ │ └── logo.js │ ├── msg │ │ └── msg.js │ ├── navlink │ │ ├── img │ │ │ ├── boss-active.png │ │ │ ├── boss.png │ │ │ ├── job-active.png │ │ │ ├── job.png │ │ │ ├── msg-active.png │ │ │ ├── msg.png │ │ │ ├── user-active.png │ │ │ └── user.png │ │ └── navlink.js │ ├── user │ │ └── user.js │ └── usercard │ │ └── usercard.js ├── config.js ├── container │ ├── consignorinfo │ │ └── consignorinfo.js │ ├── geniusinfo │ │ └── geniusinfo.js │ ├── login │ │ └── login.js │ └── register │ │ └── register.js ├── index.css ├── index.js ├── reducer.js ├── redux │ ├── chat.redux.js │ ├── chatuser.redux.js │ └── user.redux.js └── utils.js └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app" 4 | ], 5 | "plugins": [ 6 | [ 7 | "import", 8 | { 9 | "libraryName": "antd-mobile", 10 | "style": "css" 11 | } 12 | ], 13 | "transform-decorators-legacy" 14 | ] 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React16 + Redux + React-Router4 + MongoDB + Express + Socket.io Project 2 | 3 | ### Build Setup 4 | 5 | ```bash 6 | # git clone 7 | git clone https://github.com/laclys/React16-dev.git 8 | 9 | # install dependencies 10 | npm install 11 | 12 | # Mongo Daemon 13 | mongod --config /usr/local/etc/mongod.conf 14 | 15 | # start Mongo 16 | mongo 17 | 18 | # build project 19 | npm run build 20 | 21 | # run project localhost:9098 22 | npm run server 23 | 24 | ``` 25 | 26 | Features: 27 | 28 | React SSR √ 29 | 30 | Eslint √ 31 | 32 | ANIMATION √ 33 | 34 | Redux -> 4.0 35 | 36 | Redux-thunk -> Redux-saga 37 | 38 | PWA 39 | -------------------------------------------------------------------------------- /cmrh.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Same scope name as in webpack build 3 | generateScopedName: '[name]__[local]___[hash:base64:5]', 4 | } -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce((env, key) => { 82 | env[key] = JSON.stringify(raw[key]); 83 | return env; 84 | }, {}), 85 | }; 86 | 87 | return { raw, stringified }; 88 | } 89 | 90 | module.exports = getClientEnvironment; 91 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 84 | 85 | 86 | ` 87 | 88 | res.send(pageHTML) 89 | // return res.sendFile(path.resolve('build/index.html')) 90 | }) 91 | app.use('/', express.static(path.resolve('build'))) 92 | 93 | server.listen(9098, function () { 94 | console.log('Node app start at 9098') 95 | }) -------------------------------------------------------------------------------- /server/user.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const Router = express.Router() 3 | const utils = require('utility') 4 | const model = require('./model') 5 | const User = model.getModel('user') 6 | const Chat = model.getModel('chat') 7 | 8 | // Chat.remove({}, function(e,d) {}) 9 | 10 | const _filter = { 11 | 'psd': 0, 12 | '__v': 0 13 | } 14 | 15 | // 用户列表页 16 | Router.get('/list',function (req, res) { 17 | const {type} = req.query 18 | // User.remove({},function (e, d) {}) 19 | User.find({type}, function (err, doc) { 20 | return res.json({code: 0,data: doc}) 21 | }) 22 | }) 23 | 24 | Router.get('/getmsglist', function(req, res) { 25 | const user = req.cookies.userid 26 | 27 | User.find({}, function(e,userdoc) { 28 | let users = {} 29 | userdoc.forEach( v => { 30 | users[v._id] = {name: v.user, avatar: v.avatar} 31 | }) 32 | // $or多个条件 33 | Chat.find({'$or': [{from: user}, {to: user}]}, function(err, doc){ 34 | if (!err) { 35 | return res.json({code: 0, msgs: doc, users: users}) 36 | } 37 | }) 38 | }) 39 | }) 40 | 41 | Router.post('/readmsg', function(req, res) { 42 | const userid = req.cookies.userid 43 | const {from} = req.body 44 | // console.log(userid, from) 45 | Chat.update({from, to:userid}, {read:true}, {'multi': true}, function(err, doc) { 46 | // console.log(doc) 47 | if (!err) { 48 | return res.json({code: 0, num:doc.nModified}) 49 | } 50 | return res.json({code:1, msg: '修改失败'}) 51 | }) 52 | }) 53 | 54 | // 登录 55 | Router.post('/login', function (req, res) { 56 | const {user, psd} =req.body 57 | User.findOne({user, psd: md5Pwd(psd)}, _filter, function (err, doc) { 58 | if (!doc) { 59 | return res.json({ 60 | code: 1, 61 | msg: '用户名或者密码错误' 62 | }) 63 | } 64 | res.cookie('userid',doc._id) 65 | return res.json({ 66 | code: 0, 67 | data: doc 68 | }) 69 | }) 70 | }) 71 | 72 | // 注册 73 | Router.post('/register', function (req, res) { 74 | const {user, psd, type} =req.body 75 | User.findOne({user}, function (err, doc) { 76 | if (doc) { 77 | return res.json({code: 1,msg: '用户名重复'}) 78 | } 79 | const userModel = new User({user, psd: md5Pwd(psd), type}) 80 | userModel.save(function (e, d) { 81 | if (e) { 82 | return res.json({code: 1, msg: '后端出错了'}) 83 | } 84 | const {user, type, _id} = d 85 | res.cookie('userid', _id) 86 | return res.json({ 87 | code: 0, 88 | data: {user, type, _id} 89 | }) 90 | }) 91 | }) 92 | }) 93 | 94 | Router.post('/update', function (req, res) { 95 | const {userid} = req.cookies 96 | if (!userid) { 97 | return json.dumps({code: 1}) 98 | } 99 | const body = req.body 100 | User.findByIdAndUpdate(userid, body, function(err, doc){ 101 | const data = Object.assign({},{ 102 | user: doc.user, 103 | type: doc.type 104 | }, body) 105 | return res.json({code: 0, data}) 106 | }) 107 | }) 108 | 109 | // 用户有没有登录信息 110 | Router.get('/info', function (req, res) { 111 | const {userid} = req.cookies 112 | // 用户有没有cookie 113 | if (!userid) { 114 | return res.json({ 115 | code: 1 116 | }) 117 | } 118 | User.findOne({_id: userid}, _filter, function (err, doc) { 119 | console.log(err) 120 | if (err) { 121 | return res.json({ 122 | code:1, 123 | msg: '后端出错' 124 | }) 125 | } 126 | if (doc) { 127 | return res.json({ 128 | code: 0, 129 | data: doc 130 | }) 131 | } 132 | }) 133 | }) 134 | 135 | /** 136 | * 密码进行md5加密 137 | */ 138 | function md5Pwd(psd) { 139 | const salt = 'lac_is_god421084802~~' 140 | return utils.md5(utils.md5(psd + salt)) 141 | } 142 | 143 | module.exports = Router -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Dashboard from './componment/dashboard/dashboard' 3 | import Login from './container/login/login' 4 | import Register from './container/register/register' 5 | import Chat from './componment/chat/chat' 6 | import {Route, Switch} from 'react-router-dom' 7 | import AuthRoute from './componment/authroute/authroute' 8 | import ConsignorInfo from './container/consignorinfo/consignorinfo' 9 | import GeniusInfo from './container/geniusinfo/geniusinfo' 10 | 11 | class App extends React.Component { 12 | 13 | constructor(props) { 14 | super(props) 15 | this.state={ 16 | hasError: false 17 | } 18 | } 19 | 20 | componentDidCatch(err, info) { 21 | console.log(err, info) 22 | this.setState({ 23 | hasError: true 24 | }) 25 | } 26 | 27 | render () { 28 | return !this.state.hasError ? ( 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {/* 如果没有命中路由就跳转Dashboard */} 38 | 39 | 40 |
41 | ) :
页面出错了!
42 | } 43 | } 44 | export default App -------------------------------------------------------------------------------- /src/componment/authroute/authroute.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import {withRouter} from 'react-router-dom' 4 | import {loadData} from '../../redux/user.redux' 5 | import {connect} from 'react-redux' 6 | 7 | @withRouter 8 | @connect( 9 | state => state.user, 10 | {loadData} 11 | ) 12 | class AuthRoute extends React.Component { 13 | componentDidMount () { 14 | const publicList = ['/login', '/register'] 15 | const pathname = this.props.location.pathname 16 | if (publicList.indexOf(pathname) > -1) { 17 | return null 18 | } 19 | // 获取用户信息 20 | axios.get('/user/info') 21 | .then(res => { 22 | if (res.status == 200) { 23 | console.log(res.data) 24 | if (res.data.code == 0) { 25 | // 有登录信息的 26 | this.props.loadData(res.data.data) 27 | } else { 28 | // 没有登录信息的 29 | this.props.history.push('/login') 30 | } 31 | } 32 | }) 33 | } 34 | render () { 35 | return null 36 | } 37 | } 38 | 39 | export default AuthRoute -------------------------------------------------------------------------------- /src/componment/avatar-selector/avatar-selector.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Grid, List} from 'antd-mobile' 3 | import PropTypes from 'prop-types' 4 | 5 | class AvatarSelector extends React.Component { 6 | 7 | static propTypes = { 8 | selectAvatar: PropTypes.func 9 | } 10 | 11 | constructor (props) { 12 | super(props) 13 | this.state={} 14 | } 15 | 16 | render () { 17 | //头像数据列表 18 | const avatarList = '01,02,03,04,05,06,07,08' 19 | .split(',') 20 | .map(v => ({ 21 | icon: require(`../img/${v}.jpg`), 22 | text: `头像${v}` 23 | })) 24 | const gridHeader = this.state.icon 25 | ? ( 26 |
31 | 已选择图片 32 | {this.state.text}/ 33 |
34 | ) 35 | :
请选择图片
36 | return ( 37 |
38 | gridHeader} 40 | > 41 | { 45 | this.setState(elm) 46 | this.props.selectAvatar(elm.text) 47 | }} 48 | /> 49 | 50 |
51 | ) 52 | } 53 | } 54 | 55 | export default AvatarSelector -------------------------------------------------------------------------------- /src/componment/chat/chat.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {List, InputItem, NavBar, Icon, Grid} from 'antd-mobile' 3 | import {connect} from 'react-redux' 4 | import {getMsgList, sendMsg, recvMsg, readMsg} from '../../redux/chat.redux' 5 | import { getChatId } from '../../utils' 6 | import QueueAnim from 'rc-queue-anim' 7 | 8 | @connect( 9 | state=>state, 10 | {getMsgList, sendMsg, recvMsg, readMsg} 11 | ) 12 | class Chat extends React.Component{ 13 | 14 | constructor(props) { 15 | super(props) 16 | this.state= { 17 | text: '', 18 | msg: [], 19 | showEmoji: false 20 | } 21 | } 22 | 23 | componentDidMount () { 24 | if (!this.props.chat.chatmsg.length) { 25 | this.props.getMsgList() 26 | this.props.recvMsg() 27 | } 28 | this.fixCarousel() 29 | } 30 | 31 | componentWillUnmount () { 32 | // 当离开当前页面时 进行已读处理 33 | // console.log('unmount') 34 | const to = this.props.match.params.user 35 | this.props.readMsg(to) 36 | } 37 | 38 | /** 39 | * 修复ant-design中Carouse bug 40 | */ 41 | fixCarousel () { 42 | setTimeout(function() { 43 | window.dispatchEvent(new Event('resize')) 44 | }, 0) 45 | } 46 | 47 | handleSubmit () { 48 | // socket.emit('sendmsg', {text: this.state.text}) 49 | const from = this.props.user._id 50 | const to = this.props.match.params.user 51 | const msg = this.state.text 52 | this.props.sendMsg({from, to, msg}) 53 | this.setState( 54 | { 55 | text: '' 56 | } 57 | ) 58 | 59 | } 60 | 61 | render () { 62 | const emoji = '😀 😁 😂 🍇 🍉 🙈 🙉 💪 👈 🤘 🖐 ✊ 👊 🤛 👏 😎 ❤ 🗾 🏣 ⛪ ⛺ 🌁 🚕 🚀 💺 👲 🚣 🐲 🇨🇳 🌠 ☪ 🎃 🌲 ❄ 🎿 🎥 🎬 🍨 🍭' 63 | .split(' ') 64 | .filter(v => v) 65 | .map(v => ({ 66 | text: v 67 | })) 68 | const userid = this.props.match.params.user 69 | const Item = List.Item 70 | const users = this.props.chat.users 71 | if (!users[userid]) { 72 | return null 73 | } 74 | const chatid = getChatId(userid, this.props.user._id) 75 | // console.log(chatid) 76 | const chatmsg = this.props.chat.chatmsg.filter(v => v.chatid == chatid) 77 | return ( 78 |
79 | } 82 | onLeftClick={() => { 83 | this.props.history.goBack() 84 | }} 85 | > 86 | {users[userid].name} 87 | 88 |
91 | 92 | {chatmsg.map((v , i, a) => { 93 | const avatar = require(`../img/${users[v.from].avatar.slice(2)}.jpg`) 94 | return v.from==userid ? ( 95 | 96 | {v.content} 99 | 100 | ) : ( 101 | 102 | } 104 | className='chat-me' 105 | >{v.content} 106 | 107 | ) 108 | })} 109 | 110 |
111 | 112 | { 116 | this.setState({text: v}) 117 | }} 118 | extra={ 119 |
120 | { 125 | this.setState({ 126 | showEmoji: !this.state.showEmoji 127 | }) 128 | this.fixCarousel() 129 | }} 130 | >😎 131 | this.handleSubmit()} >发送 132 |
133 | } 134 | >
135 |
136 | {this.state.showEmoji ? ( 137 | { 143 | this.setState({ 144 | text: this.state.text+el.text 145 | }) 146 | }} 147 | /> 148 | ) : null} 149 |
150 |
151 | ) 152 | } 153 | } 154 | 155 | export default Chat -------------------------------------------------------------------------------- /src/componment/consignor/consignor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {getUserList} from '../../redux/chatuser.redux' 4 | import UserCard from '../../componment/usercard/usercard' 5 | 6 | @connect( 7 | state=>state.chatuser, 8 | {getUserList} 9 | ) 10 | class Consignor extends React.Component{ 11 | 12 | componentDidMount () { 13 | this.props.getUserList('genius') 14 | } 15 | render () { 16 | console.log(this.state) 17 | return 18 | } 19 | } 20 | 21 | export default Consignor -------------------------------------------------------------------------------- /src/componment/dashboard/dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {NavBar} from 'antd-mobile' 4 | import {Route} from 'react-router-dom' 5 | import NavLinkBar from '../navlink/navlink' 6 | import Consignor from '../consignor/consignor' 7 | import Genius from '../genius/genius' 8 | import User from '../../componment/user/user' 9 | import {getMsgList, recvMsg} from '../../redux/chat.redux' 10 | import Msg from '../../componment/msg/msg' 11 | import Redirect from 'react-router-dom/Redirect' 12 | 13 | @connect( 14 | state=>state, 15 | {getMsgList, recvMsg} 16 | ) 17 | class Dashboard extends React.Component { 18 | 19 | componentDidMount () { 20 | // 切换dashboard时不需要重新获取数据 21 | if (!this.props.chat.chatmsg.length) { 22 | this.props.getMsgList() 23 | this.props.recvMsg() 24 | } 25 | } 26 | 27 | render () { 28 | const {pathname} = this.props.location 29 | console.log(pathname) 30 | const user = this.props.user 31 | 32 | const navList = [ 33 | { 34 | path: '/consignor', 35 | text: '牛人', 36 | icon: 'boss', 37 | title: '牛人列表', 38 | component: Consignor, 39 | hide: user.type == 'genius' 40 | }, 41 | { 42 | path: '/genius', 43 | text: '委托人', 44 | icon: 'job', 45 | title: '任务列表', 46 | component: Genius, 47 | hide: user.type == 'consignor' 48 | }, 49 | { 50 | path: '/msg', 51 | text: '消息', 52 | icon: 'msg', 53 | title: '消息列表', 54 | component: Msg 55 | }, 56 | { 57 | path: '/me', 58 | text: '我', 59 | icon: 'user', 60 | title: '个人中心', 61 | component: User 62 | } 63 | ] 64 | const page = navList.find(v=>v.path==pathname) 65 | return page ? ( 66 |
67 | {page.title} 68 |
69 | 70 |
71 | 72 |
73 | 74 | ): 75 | } 76 | } 77 | 78 | export default Dashboard -------------------------------------------------------------------------------- /src/componment/genius/genius.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {getUserList} from '../../redux/chatuser.redux' 4 | import UserCard from '../../componment/usercard/usercard' 5 | 6 | @connect( 7 | state=>state.chatuser, 8 | {getUserList} 9 | ) 10 | class Genius extends React.Component{ 11 | 12 | componentDidMount () { 13 | this.props.getUserList('consignor') 14 | } 15 | render () { 16 | console.log(this.state) 17 | return 18 | } 19 | } 20 | 21 | export default Genius -------------------------------------------------------------------------------- /src/componment/hoc-form/hoc-form.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function hocForm(Comp) { 4 | return class WrapperComp extends React.Component { 5 | 6 | constructor(props) { 7 | super(props) 8 | this.state = {} 9 | this.handleChange = this.handleChange.bind(this) 10 | } 11 | 12 | handleChange (key, val) { 13 | this.setState({ 14 | [key]: val 15 | }) 16 | } 17 | 18 | render() { 19 | return 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/componment/img/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/01.jpg -------------------------------------------------------------------------------- /src/componment/img/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/02.jpg -------------------------------------------------------------------------------- /src/componment/img/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/03.jpg -------------------------------------------------------------------------------- /src/componment/img/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/04.jpg -------------------------------------------------------------------------------- /src/componment/img/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/05.jpg -------------------------------------------------------------------------------- /src/componment/img/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/06.jpg -------------------------------------------------------------------------------- /src/componment/img/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/07.jpg -------------------------------------------------------------------------------- /src/componment/img/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/img/08.jpg -------------------------------------------------------------------------------- /src/componment/logo/logo.css: -------------------------------------------------------------------------------- 1 | .logo-container{ 2 | margin-top: 50px; 3 | text-align: center; 4 | margin-bottom: 20px; 5 | } 6 | .logo-img{ 7 | width: 200px; 8 | height: 200px; 9 | border-radius: 100px; 10 | } -------------------------------------------------------------------------------- /src/componment/logo/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/logo/logo.jpg -------------------------------------------------------------------------------- /src/componment/logo/logo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './logo.css' 3 | class Logo extends React.Component { 4 | 5 | render () { 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | } 13 | 14 | export default Logo -------------------------------------------------------------------------------- /src/componment/msg/msg.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {List, Badge} from 'antd-mobile' 4 | 5 | @connect( 6 | state => state 7 | ) 8 | class Msg extends React.Component { 9 | 10 | getLast (arr) { 11 | return arr[arr.length-1] 12 | } 13 | 14 | render () { 15 | if (!this.props.chat.chatmsg.length) { 16 | return null 17 | } 18 | const Item = List.Item 19 | const Brief = Item.Brief 20 | const userid = this.props.user._id 21 | const masgGroup = {} 22 | this.props.chat.chatmsg.forEach(v => { 23 | masgGroup[v.chatid] = masgGroup[v.chatid] || [] 24 | masgGroup[v.chatid].push(v) 25 | }) 26 | 27 | const chatList = Object.values(masgGroup).sort((a, b) => { 28 | const a_last = this.getLast(a).create_time 29 | const b_last = this.getLast(b).create_time 30 | return b_last - a_last 31 | }) 32 | 33 | 34 | // 根据chatid,用户分组 35 | console.log(chatList) 36 | return ( 37 |
38 | {chatList.map(v => { 39 | const lastItem = this.getLast(v) 40 | const targetId = v[0].from == userid ? v[0].to : v[0].from 41 | const name = this.props.chat.users[targetId] ? this.props.chat.users[targetId].name : '' 42 | const avatar = this.props.chat.users[targetId] ? this.props.chat.users[targetId].avatar : '' 43 | const unreadNum = v.filter(v => !v.read && v.to == userid).length 44 | return ( 45 | 48 | } 51 | thumb={require(`../img/${avatar.slice(2)}.jpg`)} 52 | arrow='horizontal' 53 | onClick={() => { 54 | this.props.history.push(`/chat/${targetId}`) 55 | }} 56 | > 57 | {lastItem.content} 58 | {name} 59 | 60 | 61 | )})} 62 |
63 | ) 64 | } 65 | } 66 | 67 | export default Msg -------------------------------------------------------------------------------- /src/componment/navlink/img/boss-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/boss-active.png -------------------------------------------------------------------------------- /src/componment/navlink/img/boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/boss.png -------------------------------------------------------------------------------- /src/componment/navlink/img/job-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/job-active.png -------------------------------------------------------------------------------- /src/componment/navlink/img/job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/job.png -------------------------------------------------------------------------------- /src/componment/navlink/img/msg-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/msg-active.png -------------------------------------------------------------------------------- /src/componment/navlink/img/msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/msg.png -------------------------------------------------------------------------------- /src/componment/navlink/img/user-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/user-active.png -------------------------------------------------------------------------------- /src/componment/navlink/img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laclys/React16-dev/d556dcb3bfa771bd378981bc0bb9b96ea07a5346/src/componment/navlink/img/user.png -------------------------------------------------------------------------------- /src/componment/navlink/navlink.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {TabBar} from 'antd-mobile' 3 | import PropTypes from 'prop-types' 4 | import { withRouter } from 'react-router-dom' 5 | import {connect} from 'react-redux' 6 | 7 | @withRouter 8 | @connect( 9 | state => state.chat 10 | ) 11 | class NavLinkBar extends React.Component { 12 | static propTypes = { 13 | selectAvatar: PropTypes.array 14 | } 15 | render (){ 16 | const navList = this.props.data.filter(v => !v.hide) 17 | const {pathname} = this.props.location 18 | return ( 19 | 20 | {navList.map(v => { 21 | return ( 22 | { 30 | this.props.history.push(v.path) 31 | }} 32 | > 33 | 34 | 35 | ) 36 | })} 37 | 38 | ) 39 | } 40 | } 41 | 42 | export default NavLinkBar -------------------------------------------------------------------------------- /src/componment/user/user.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import {Result, List, WhiteSpace, Modal} from 'antd-mobile' 4 | import browserCookie from 'browser-cookies' 5 | import {logoutSubmit} from '../../redux/user.redux' 6 | import {Redirect} from 'react-router-dom' 7 | 8 | @connect( 9 | state => state.user, 10 | {logoutSubmit} 11 | ) 12 | class User extends React.Component { 13 | 14 | constructor(props) { 15 | super(props) 16 | this.logout = this.logout.bind(this) 17 | } 18 | 19 | logout() { 20 | const alert = Modal.alert 21 | alert('注销', '确认注销吗???', [ 22 | { text: '取消', onPress: () => console.log('cancel') }, 23 | { text: '确认', onPress: () => { 24 | browserCookie.erase('userid') // 清除cookie 25 | this.props.logoutSubmit() 26 | }}, 27 | ]) 28 | } 29 | 30 | render () { 31 | const props = this.props 32 | const Item = List.Item 33 | const Brief = Item.Brief 34 | return props.user ? ( 35 |
36 | } 38 | title={props.user} 39 | message={props.type == 'consignor' ? props.company : null} 40 | > 41 | '简介'} > 42 | 45 | {props.title} 46 | {props.desc.split('\n').map((v, i, a) => {props.desc})} 47 | {props.money ? Money: {props.money} : null} 48 | 49 | 50 | 51 | 52 | 注销 53 | 54 |
55 | ) : 56 | } 57 | } 58 | 59 | export default User -------------------------------------------------------------------------------- /src/componment/usercard/usercard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import {Card, WhiteSpace, WingBlank} from 'antd-mobile' 4 | import {withRouter} from 'react-router-dom' 5 | 6 | @withRouter 7 | class UserCard extends React.Component { 8 | static propTypes = { 9 | userlist: PropTypes.array.isRequired 10 | } 11 | 12 | handleClick (v) { 13 | this.props.history.push(`/chat/${v._id}`) 14 | } 15 | 16 | render () { 17 | return ( 18 | 19 | 20 | {this.props.userlist.map(v => { 21 | return ( 22 | v.avatar 23 | ? this.handleClick(v)} 27 | > 28 | } 31 | extra={{v.title}} 32 | > 33 | 34 | 35 | {v.type =='consignor' ?
委托人:{v.company}
: null} 36 | {v.desc.split('\n').map(d => { 37 | return ( 38 |
{d}
39 | ) 40 | })} 41 | {v.type =='consignor' ?
报酬:{v.money}
: null} 42 |
43 |
: null 44 | ) 45 | })} 46 |
47 | ) 48 | } 49 | } 50 | 51 | export default UserCard -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import {Toast} from 'antd-mobile' 3 | 4 | // 拦截请求 5 | axios.interceptors.request.use(function (config) { 6 | Toast.loading('加载中', 0) 7 | return config 8 | }) 9 | 10 | // 拦截响应 11 | axios.interceptors.response.use(function (config) { 12 | Toast.hide() 13 | return config 14 | }) -------------------------------------------------------------------------------- /src/container/consignorinfo/consignorinfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile' 3 | import AvatarSelector from '../../componment/avatar-selector/avatar-selector' 4 | import {connect} from 'react-redux' 5 | import {update} from '../../redux/user.redux' 6 | import {Redirect} from 'react-router-dom' 7 | 8 | 9 | @connect( 10 | state => state.user, 11 | {update} 12 | ) 13 | class ConsignorInfo extends React.Component { 14 | 15 | constructor (props) { 16 | super (props) 17 | this.state = { 18 | title: '', 19 | desc: '', 20 | company: '', 21 | money: '' 22 | } 23 | } 24 | 25 | onChange (key, val) { 26 | this.setState({ 27 | [key]: val 28 | }) 29 | } 30 | 31 | render () { 32 | const path = this.props.location.pathname 33 | const redirect = this.props.redirectTo 34 | return ( 35 |
36 | {redirect && redirect !== path? : null} 37 | 委托人完善信息 38 | { 40 | this.setState({ 41 | avatar: imgname 42 | }) 43 | }} 44 | > 45 | this.onChange('title', v)} > 46 | 委托任务 47 | 48 | this.onChange('company', v)} > 49 | 委托人名 50 | 51 | this.onChange('money', v)} > 52 | 薪资 53 | 54 | this.onChange('desc', v)} 56 | rows={3} 57 | autoHeight 58 | title='任务要求' 59 | > 60 | 61 | 65 |
66 | ) 67 | } 68 | } 69 | 70 | export default ConsignorInfo -------------------------------------------------------------------------------- /src/container/geniusinfo/geniusinfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile' 3 | import AvatarSelector from '../../componment/avatar-selector/avatar-selector' 4 | import {connect} from 'react-redux' 5 | import {update} from '../../redux/user.redux' 6 | import {Redirect} from 'react-router-dom' 7 | 8 | 9 | @connect( 10 | state => state.user, 11 | {update} 12 | ) 13 | class GeniusInfo extends React.Component { 14 | 15 | constructor (props) { 16 | super (props) 17 | this.state = { 18 | title: '', 19 | desc: '' 20 | } 21 | } 22 | 23 | onChange (key, val) { 24 | this.setState({ 25 | [key]: val 26 | }) 27 | } 28 | 29 | render () { 30 | const path = this.props.location.pathname 31 | const redirect = this.props.redirectTo 32 | return ( 33 |
34 | {redirect && redirect !== path? : null} 35 | 牛人完善信息 36 | { 38 | this.setState({ 39 | avatar: imgname 40 | }) 41 | }} 42 | > 43 | this.onChange('title', v)} > 44 | 跑腿技能 45 | 46 | this.onChange('desc', v)} 48 | rows={3} 49 | autoHeight 50 | title='个人简介' 51 | > 52 | 53 | 57 |
58 | ) 59 | } 60 | } 61 | 62 | export default GeniusInfo -------------------------------------------------------------------------------- /src/container/login/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Redirect} from 'react-router-dom' 3 | 4 | import Logo from '../../componment/logo/logo' 5 | import {List, InputItem, WingBlank, WhiteSpace,Button} from 'antd-mobile' 6 | import {connect} from 'react-redux' 7 | import {login} from '../../redux/user.redux' 8 | import hocForm from '../../componment/hoc-form/hoc-form' 9 | 10 | @connect( 11 | state => state.user, 12 | {login} 13 | ) 14 | @hocForm 15 | class Login extends React.Component { 16 | 17 | handleLogin () { 18 | this.props.login(this.props.state) 19 | } 20 | 21 | register () { 22 | this.props.history.push('/register') 23 | } 24 | 25 | render () { 26 | return ( 27 |
28 | {(this.props.redirectTo && this.props.redirectTo !== '/login') ? : null} 29 | 30 | 31 | 32 | {this.props.msg ?

{this.props.msg}

: null} 33 | { 35 | this.props.handleChange('user', v) 36 | }} 37 | > 38 | 用户 39 | 40 | { 43 | this.props.handleChange('psd', v) 44 | }} 45 | >密码 46 |
47 | 51 | 52 | 56 |
57 |
58 | ) 59 | } 60 | } 61 | 62 | export default Login -------------------------------------------------------------------------------- /src/container/register/register.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Logo from '../../componment/logo/logo' 4 | import {List, InputItem, WhiteSpace,Button, Radio} from 'antd-mobile' 5 | import {connect} from 'react-redux' 6 | import {register} from '../../redux/user.redux' 7 | import { Redirect } from 'react-router-dom' 8 | import hocForm from '../../componment/hoc-form/hoc-form' 9 | 10 | @connect( 11 | state => state.user, 12 | {register} 13 | ) 14 | @hocForm 15 | class Register extends React.Component { 16 | 17 | componentDidMount () { 18 | this.props.handleChange('type', 'genius') 19 | } 20 | 21 | handleRegister () { 22 | this.props.register(this.props.state) 23 | console.log(this.props.state) 24 | } 25 | 26 | render () { 27 | const RadioItem = Radio.RadioItem 28 | return ( 29 |
30 | {this.props.redirectTo ? : null} 31 | 32 |

注册页

33 | 34 | {this.props.msg ?

{this.props.msg}

: null} 35 | { 37 | this.props.handleChange('user', v) 38 | }} 39 | >用户名 40 | 41 | { 44 | this.props.handleChange('psd', v) 45 | }} 46 | >密码 47 | 48 | { 51 | this.props.handleChange('repeatpsd', v) 52 | }} 53 | >确认密码 54 | 55 | this.props.handleChange('type', 'genius')} 58 | >牛人 59 | this.props.handleChange('type', 'consignor')} 62 | >委托人 63 | 64 | 68 |
69 |
70 | ) 71 | } 72 | } 73 | 74 | export default Register -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | 2 | .error-msg { 3 | color: #f50; 4 | padding-left: 10px; 5 | } 6 | 7 | .am-tab-bar { 8 | position: fixed; 9 | bottom: 0; 10 | width: 100%; 11 | } 12 | 13 | .fixed-header, .am-navbar { 14 | position: fixed; 15 | top: 0; 16 | width: 100% 17 | } 18 | 19 | .page-content { 20 | padding: 45px 5px 21 | } 22 | 23 | #chat-page .chat-me .am-list-extra{ 24 | flex-basis:auto; 25 | } 26 | #chat-page .chat-me .am-list-content{ 27 | padding-right: 15px; 28 | text-align: right; 29 | } 30 | #chat-page .am-grid-icon{ 31 | display: none; 32 | } 33 | #chat-page .am-grid-text{ 34 | margin-top: 0; 35 | } 36 | .stick-footer{ 37 | z-index:10; 38 | position: fixed; 39 | bottom: 0; 40 | width:100%; 41 | } 42 | #chat-page .am-navbar{ 43 | z-index: 10 44 | } 45 | 46 | .error-container{ 47 | position: absolute; 48 | top: 0; 49 | bottom: 0; 50 | left: 0; 51 | right: 0; 52 | height: 20px; 53 | width: 100px; 54 | margin: auto; 55 | text-align: center; 56 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | import {createStore, applyMiddleware, compose} from 'redux' 4 | import thunk from 'redux-thunk' 5 | import {Provider} from 'react-redux' 6 | import {BrowserRouter} from 'react-router-dom' 7 | import App from './app' 8 | 9 | import './config' 10 | import './index.css' 11 | import reducers from './reducer' 12 | 13 | // const reduxDevtools = window.devToolsExtension ? window.devToolsExtension() : () => {} 14 | 15 | const store = createStore(reducers,compose(applyMiddleware(thunk), window.devToolsExtension?window.devToolsExtension():f=>f)) 16 | 17 | // exact表示完全匹配 路由渲染对应的模板 18 | ReactDom.render( 19 | ( 20 | 21 | 22 | 23 | ), 24 | document.getElementById('root') 25 | ) 26 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | // 合并所有的reducer 并且返回 2 | 3 | import {combineReducers} from 'redux' 4 | import {user} from './redux/user.redux' 5 | import {chatuser} from './redux/chatuser.redux' 6 | import {chat} from './redux/chat.redux' 7 | 8 | export default combineReducers({user, chatuser, chat}) -------------------------------------------------------------------------------- /src/redux/chat.redux.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import io from 'socket.io-client' 3 | const socket = io('ws://localhost:9098') 4 | 5 | // 获取聊天列表 6 | const MSG_LIST = 'MSG_LIST' 7 | 8 | // 读取信息 9 | const MSG_RECV = 'MSG_RECV' 10 | 11 | // 标识已读 12 | const MSG_READ = 'MSG_READ' 13 | 14 | const initState = { 15 | chatmsg: [], 16 | unread: 0 , 17 | users: {} 18 | } 19 | 20 | export function chat (state = initState, action) { 21 | switch(action.type) { 22 | case MSG_LIST: 23 | return {...state, users: action.payload.users, chatmsg: action.payload.msgs, unread: action.payload.msgs.filter(v => !v.read && v.to==action.payload.userid).length} 24 | case MSG_RECV: 25 | const n = action.payload.to == action.userid ? 1 : 0 26 | return {...state, chatmsg:[...state.chatmsg, action.payload], unread: state.unread + n} 27 | case MSG_READ: 28 | const {from, num} = action.payload 29 | return {...state, chatmsg: state.chatmsg.map(v => { 30 | if (from == v.from) { 31 | v.read = true 32 | } 33 | return v 34 | }), unread: state.unread - num} 35 | default: 36 | return state 37 | } 38 | } 39 | 40 | function msgList (msgs, users, userid) { 41 | return {type: 'MSG_LIST', payload:{msgs, users, userid}} 42 | } 43 | 44 | function msgRecv (msg, userid) { 45 | return {userid, type: MSG_RECV, payload: msg} 46 | } 47 | 48 | export function sendMsg ({from, to, msg}) { 49 | return dispatch => { 50 | socket.emit('sendmsg',{from, to, msg}) 51 | } 52 | } 53 | 54 | export function recvMsg () { 55 | return (dispatch, getState) => { 56 | socket.on('recvmsg',function(data) { 57 | const userid = getState().user._id // 获取当前登录的用户id 58 | dispatch(msgRecv(data, userid)) 59 | }) 60 | } 61 | } 62 | 63 | export function getMsgList () { 64 | return async (dispatch, getState) => { 65 | const res = await axios.get('/user/getmsglist') 66 | if (res.status === 200 && res.data.code === 0) { 67 | const userid = getState().user._id // 获取当前登录的用户id 68 | dispatch(msgList(res.data.msgs, res.data.users, userid)) 69 | } 70 | } 71 | } 72 | 73 | function msgRead({from, userid, num}) { 74 | return {type: MSG_READ, payload: {from, userid, num}} 75 | } 76 | 77 | export function readMsg(from) { 78 | return async (dispatch, getState) => { 79 | const res = await axios.post('/user/readmsg', {from}) 80 | const userid = getState().user._id 81 | if (res.status == 200 && res.data.code == 0) { 82 | dispatch(msgRead({userid, from, num: res.data.num})) 83 | } 84 | 85 | 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /src/redux/chatuser.redux.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const USER_LIST ='USER_LIST' 4 | 5 | 6 | const initState = { 7 | userlist: [] 8 | } 9 | 10 | export function chatuser(state=initState, action) { 11 | switch(action.type) { 12 | case USER_LIST: 13 | return {...state, userlist: action.payload} 14 | default: 15 | return state 16 | } 17 | } 18 | 19 | function userList(data) { 20 | return {type: USER_LIST, payload: data} 21 | } 22 | 23 | export function getUserList(type) { 24 | return dispatch => { 25 | axios.get('/user/list?type='+ type) 26 | .then(res => { 27 | if (res.data.code == 0) { 28 | dispatch(userList(res.data.data)) 29 | } 30 | }) 31 | } 32 | } -------------------------------------------------------------------------------- /src/redux/user.redux.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios' 3 | import {getRedirectPath} from '../utils' 4 | 5 | const ERROR_MSG = 'ERROR_MSG' 6 | const LOAD_DATA = 'LOAD_DATA' 7 | const AUTH_SUCCESS = 'AUTH_SUCCESS' 8 | const LOGOUT = 'LOGOUT' 9 | 10 | const initState = { 11 | redirectTo: '', 12 | msg: '', 13 | user: '', 14 | type: '' 15 | } 16 | 17 | // reducer 18 | export function user (state = initState, action) { 19 | 20 | switch (action.type) { 21 | case AUTH_SUCCESS: 22 | return {...state, msg: '',redirectTo: getRedirectPath(action.payload), ...action.payload} 23 | case ERROR_MSG: 24 | return {...state, isAuth: false, msg: action.msg} 25 | case LOAD_DATA: 26 | return {...state,...action.payload} 27 | case LOGOUT: 28 | return {...initState, redirectTo: '/login'} 29 | default: 30 | return state 31 | } 32 | } 33 | 34 | function authSuccess(obj) { 35 | const {psd, ...data} = obj // 过滤掉psd 36 | return {type: AUTH_SUCCESS, payload: data} 37 | } 38 | 39 | function errorMsg(msg) { 40 | return {msg, type: ERROR_MSG} 41 | } 42 | 43 | export function loadData(userinfo) { 44 | return {type: LOAD_DATA, payload: userinfo} 45 | } 46 | 47 | export function login({user, psd}) { 48 | if (!user || !psd) { 49 | return errorMsg('用户名密码必须输入') 50 | } 51 | return dispatch => { 52 | axios.post('/user/login', {user, psd}) 53 | .then(res => { 54 | if (res.status == 200 && res.data.code === 0) { 55 | dispatch(authSuccess(res.data.data)) 56 | } else { 57 | dispatch(errorMsg(res.data.msg)) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | export function register({user, psd, repeatpsd, type}) { 64 | if (!user || !psd || !type) { 65 | return errorMsg('用户名密码必须输入') 66 | } 67 | if (psd !== repeatpsd) { 68 | return errorMsg('密码和确认密码不同') 69 | } 70 | return dispatch => { 71 | axios.post('/user/register', {user, psd, type}) 72 | .then(res => { 73 | if (res.status == 200 && res.data.code === 0) { 74 | dispatch(authSuccess({user, psd, type})) 75 | } else { 76 | dispatch(errorMsg(res.data.msg)) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | export function update(data) { 83 | return dispatch => { 84 | axios.post('/user/update', data) 85 | .then(res => { 86 | if (res.status == 200 && res.data.code === 0) { 87 | dispatch(authSuccess(res.data.data)) 88 | } else { 89 | dispatch(errorMsg(res.data.msg)) 90 | } 91 | }) 92 | } 93 | } 94 | 95 | export function logoutSubmit() { 96 | return {type: LOGOUT} 97 | } 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function getRedirectPath({type, avatar}) { 2 | // 根据用户信息 返回跳转地址 3 | let url = (type === 'consignor') ? '/consignor' : '/genius' 4 | if (!avatar) { 5 | url += 'info' 6 | } 7 | return url 8 | } 9 | 10 | export function getChatId(userId, targetId) { 11 | return [userId, targetId].sort().join('-') 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "allowJs": true 5 | } 6 | } --------------------------------------------------------------------------------