导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. 生成项目
  • 2. 实现注册和登录
    • 2.1 User/_layout.js
    • 2.2 User/User.less
    • 2.3 User/Register.js
    • 2.4 User/Register.less
  • 3. 实现手机验证码验证
    • 3.1 request.js
    • 3.2 User/Register.js
    • 3.3 models/register.js
    • 3.4 services/register.js
    • 3.5 server/app.js
    • 3.6 routes/user.js
    • 3.7 server/config.js
  • 4.手机注册和登录
    • 4.1 server/app.js
    • 4.2 server/config.js
    • 4.3 models/index.js
    • 4.4 routes/user.js
    • 4.5 Rooms/index.jsx
    • 4.6 User/Login.jsx
    • 4.7 User/Login.less
    • 4.8 User/Register.jsx
    • 4.9 models/login.js
    • 4.10 models/register.js
    • 4.11 services/login.js
    • 4.12 services/register.js
    • 4.13 utils/request.js
  • 5. 实现房间功能
    • 5.1 server/app.js
    • 5.2 models/index.js
    • 5.3 routes/room.js
    • 5.4 Room/index.jsx
    • 5.5 Room/index.less
    • 5.6 models/room.js
    • 5.7 services/room.js
  • 6. 实现聊天功能
    • 6.1 server/app.js
    • 6.2 models/index.js
    • 6.3 routes/user.js
    • 6.4 Room/$id.jsx
    • 6.5 Room/index.jsx
    • 6.6 models/chat.js
    • 6.7 models/room.js
    • 6.8 models/login.js
  • 7. 上传图片
    • 7.1 server/app.js
    • 7.2 models/index.js
    • 7.3 routes/room.js
    • 7.4 routes/user.js
    • 7.5 Room/index.jsx
    • 7.6 /models/chat.js
    • 7.7 models/room.js
    • 7.8 models/login.js
    • 7.9 services/login.js
  • 参考链接

1. 生成项目 #

$ npm install dva-cli -g
$ dva -v
$ dva new zhufeng-dva-chat

2. 实现注册和登录 #

2.1 User/_layout.js #

src/pages/User/_layout.js

import React,{Component} from 'react';
import styles from './User.less';
export default class UserLayout extends Component {
    render() {
        return (
            <div className={styles.container}>
                {this.props.children}
            </div>
        )
    }
}

2.2 User/User.less #

src/pages/User/User.less

.container{
    display: flex;
    height:100%;
    justify-content: center;
    align-items: center;
}

2.3 User/Register.js #

src/pages/User/Register.js

import React,{Component} from 'react';
import {connect} from 'dva';
import Link from 'umi/link';
import { Form, Input, Button, Select, Row, Col, Popover, Progress } from 'antd';
import styles from './Register.less';
const FormItem = Form.Item;
const { Option } = Select;
const InputGroup=Input.Group;

const passwordStatusMap = {
    ok: (
      <div className={styles.success}>
        强度: 强
      </div>
    ),
    pass: (
      <div className={styles.warning}>
        v强度: 中
      </div>
    ),
    poor: (
      <div className={styles.error}>
        强度: 太短
      </div>
    ),
};
const passwordProgressMap = {
    ok: 'success',
    pass: 'normal',
    poor: 'exception',
  };


@connect(({ register, loading }) => ({
    register,
    submitting: loading.effects['register/submit'],
  }))
@Form.create()
export default class extends Component {
    state = {
        count: 0,
        confirmDirty: false,
        visible: false,
        help: '',
        prefix: '86',
    };
    getPasswordStatus=() => {
        const { form } = this.props;
        const value = form.getFieldValue('password');
        if (value && value.length > 9) {
            return 'ok';
        }
        if (value && value.length > 5) {
            return 'pass';
        }
        return 'poor';
    };

    renderPasswordProgress = () => {
        const { form } = this.props;
        const value = form.getFieldValue('password');
        const passwordStatus = this.getPasswordStatus();
        return value && value.length ? (
          <div className={styles[`progress-${passwordStatus}`]}>
            <Progress
              status={passwordProgressMap[passwordStatus]}
              className={styles.progress}
              strokeWidth={6}
              percent={value.length * 10 > 100 ? 100 : value.length * 10}
              showInfo={false}
            />
          </div>
        ) : null;
      };

      checkPassword = (rule, value, callback) => {
        const { visible, confirmDirty } = this.state;
        if (!value) {
          this.setState({
            help:"请输入密码!",
            visible: !!value,
          });
          callback('error');
        } else {
          this.setState({
            help: '',
          });
          if (!visible) {
            this.setState({
              visible: !!value,
            });
          }
          if (value.length < 6) {
            callback('error');
          } else {
            const { form } = this.props;
            if (value && confirmDirty) {
              form.validateFields(['confirm'], { force: true });
            }
            callback();
          }
        }
      };
    checkConfirm = (rule, value, callback) => {
        const { form } = this.props;
        if (value && value !== form.getFieldValue('password')) {
          callback('两次输入的密码不匹配!');
        } else {
          callback();
        }
    };
    changePrefix = value => {
        this.setState({
          prefix: value,
        });
    };
    onGetCaptcha = () => {
        let count = 59;
        this.setState({ count });
        this.interval = setInterval(() => {
          count -= 1;
          this.setState({ count });
          if (count === 0) {
            clearInterval(this.interval);
          }
        }, 1000);
      };
    render() {
        const { form, submitting } = this.props;
        const {getFieldDecorator}=form;
        const { count, prefix, help, visible } = this.state;
        return (
            <div className={styles.register}>
                <h3>用户注册</h3>
                <Form onSubmit={this.handleSubmit}>
                    <FormItem>
                        {getFieldDecorator('mail', {
                            rules: [
                                {
                                required: true,
                                message:  '邮箱必须输入',
                                },
                                {
                                type: 'email',
                                message: '邮箱格式不合法',
                                },
                            ],
                        })(
                        <Input size="large" placeholder="邮箱"/>
                        )}
                    </FormItem>
                    <FormItem help={help}>
                        <Popover
                        content={
                            <div style={{ padding: '4px 0' }}>
                                {passwordStatusMap[this.getPasswordStatus()]}
                                {this.renderPasswordProgress()}
                                <div style={{ marginTop: 10 }}>
                                  请至少输入 6 个字符。请不要使用容易被猜到的密码。
                                </div>
                            </div>
                        }
                        overlayStyle={{ width: 240 }}
                        placement="right"
                        visible={visible}
                        >
                        {getFieldDecorator('password', {
                            rules: [
                            {
                                validator: this.checkPassword,
                            },
                            ],
                        })(
                            <Input
                              size="large"
                              type="password"
                              placeholder='至少六位密码,区分大小写'
                            />
                        )}
                        </Popover>
                    </FormItem>
                <FormItem>
                    {getFieldDecorator('confirm', {
                    rules: [
                        {
                         required: true,
                         message: '请输入确认密码!',
                        },
                        {
                         validator: this.checkConfirm,
                        },
                    ],
                    })(
                    <Input
                        size="large"
                        type="password"
                        placeholder="确认密码"
                    />
                    )}
                </FormItem>
                <FormItem>
                    <InputGroup compact>
                    <Select
                        size="large"
                        value={prefix}
                        onChange={this.changePrefix}
                        style={{ width: '20%' }}
                    >
                        <Option value="86">+86</Option>
                        <Option value="87">+87</Option>
                    </Select>
                    {getFieldDecorator('mobile', {
                        rules: [
                        {
                            required: true,
                            message: '请输入手机号!',
                        },
                        {
                            pattern: /^\d{11}$/,
                            message: '手机号格式错误!',
                        },
                        ],
                    })(
                        <Input
                        size="large"
                        style={{ width: '80%' }}
                        placeholder='手机号'
                        />
                    )}
                    </InputGroup>
                </FormItem>
                <FormItem>
                    <Row gutter={8}>
                        <Col span={16}>
                            {getFieldDecorator('captcha', {
                            rules: [
                                {
                                required: true,
                                message: '请输入验证码!',
                                },
                            ],
                            })(
                            <Input
                                size="large"
                                placeholder="验证码"
                            />
                            )}
                        </Col>
                        <Col span={8}>
                            <Button
                            size="large"
                            disabled={count}
                            className={styles.getCaptcha}
                            onClick={this.onGetCaptcha}
                            >
                              {count? `${count} s`: '获取验证码'}
                            </Button>
                        </Col>
                    </Row>
                </FormItem>
                <FormItem>
                    <Button
                    size="large"
                    loading={submitting}
                    className={styles.submit}
                    type="primary"
                    htmlType="submit"
                    >
                    注册
                    </Button>
                    <Link className={styles.login} to="/User/Login">
                      使用已有账户登录
                    </Link>
                </FormItem>
              </Form>
            </div>

        );
    }
}

2.4 User/Register.less #

src/pages/User/Register.less

.register{
    width:450px;
    border:1px solid #CCC;
    border-radius: 10px;
    padding:20px;
    h3{
        text-align: center;
    }
    .getCaptcha{
        width:100%;
        display:block;
    }
    .login{
        float:right;
    }
}

3. 实现手机验证码验证 #

3.1 request.js #

import fetch from 'dva/fetch';

function parseJSON(response) {
  return response.json();
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

function checkSuccess(data) {
  if (data.code == 0) {
    return data;
  }

  const error = new Error(data.error);
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
const HOST='http://localhost:3000';
export default function request(url, options) {
  return fetch(HOST+url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(checkSuccess)
    .catch(err => ({ err }));
}

3.2 User/Register.js #

src/pages/User/Register.js

-    onGetCaptcha = () => {
+    onGetCaptcha=() => {
+        const {dispatch,form}=this.props;
+        let mobile=form.getFieldValue('mobile');
+        dispatch({
+            type: 'register/getCaptcha',
+            payload:mobile
+        });

3.3 models/register.js #

src/pages/User/models/register.js

import * as registerService from '../services/register';
export default {
  namespace: 'register',
  state: {

  },
  effects: {
      *getCaptcha({payload},{call,put}) {
        const {data} = yield call(registerService.getCaptcha,payload);
        console.log(data);
    }
  },
  reducers: {}
};

3.4 services/register.js #

src/pages/User/services/register.js

import request from '../../../utils/request';
export function getCaptcha(mobile) {
  return request(`/user/getCaptcha?mobile=${mobile}`);
}

3.5 server/app.js #

server/app.js

let express=require('express');
let session=require('express-session');
const cors=require('cors');
let app=express();
app.use(cors());
app.use(session({
    resave:true,
    secret:'zfpx',
    saveUninitialized:true
}));
let user=require('./routes/user');
app.use('/user',user);
app.listen(3000);

3.6 routes/user.js #

server/routes/user.js

let express=require('express');
let router=express.Router();
let axios=require('axios');
let {SMS}=require('../config');
router.get('/getCaptcha',async function (req,res) {
    let {mobile}=req.query;
    let captcha=new Date().getMilliseconds();
    req.session.captcha = captcha;
    let {data} = await axios({
        method: 'POST',
        url: 'https://open.ucpaas.com/ol/sms/sendsms',
        data: {
            ...SMS,
            mobile,
            param:captcha,
        },
        headers: {
            "Content-Type": "application/json;charset=utf-8",
            "Accept": "application/json"
        }
    });
    if (data.code == '000000') {
        res.json({code: 0,data:captcha});
    } else {
        res.json({code:1,error:'获取验证码失败!'});
    }
});
module.exports=router;

3.7 server/config.js #

server/config.js

module.exports={
    SMS: {
        sid: '32548fb951ac0df279db0e6e9a515566@',   //开发者账号id 去掉@
    token: 'aa0309c08920ca38201de69eb3c745b6@', //开发者token
    appid: '16129d504b7c484c9e8f09b4ec929983@', //应用id
        templateid: '387675',                      //短信模板id
    }
}

4.手机注册和登录 #

4.1 server/app.js #

server/app.js

+const bodyParser=require('body-parser');
 let app=express();
-app.use(cors());
+app.use(cors({
+    origin:'http://localhost:8000',
+    credentials:true
+}));
+app.use(bodyParser.json());

4.2 server/config.js #

server/config.js

+    DB: {
+        url:'mongodb://localhost/chat'
+    }

4.3 models/index.js #

server/models/index.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
const {
  DB
} = require('../config');
const conn = mongoose.createConnection(DB.url,{ useNewUrlParser: true });
const User = conn.model('User', {
    email: {type: String,required: true},
    password: {type: String,required: true},
    mobile:{type:String,required:true}
});
module.exports={
    User
}

4.4 routes/user.js #

server/routes/user.js

let express=require('express');
let router=express.Router();
let axios=require('axios');
let {SMS}=require('../config');
const {User}=require('../models');
router.get('/getCaptcha',async function (req,res) {
    let {mobile}=req.query;
    let captcha=new Date().getMilliseconds();
    req.session.mobile=mobile;
    req.session.captcha=captcha;
    let {data} = await axios({
        method: 'POST',
        url: 'https://open.ucpaas.com/ol/sms/sendsms',
        data: {
            ...SMS,
            mobile,
            param:captcha,
        },
        headers: {
            "Content-Type": "application/json;charset=utf-8",
            "Accept": "application/json"
        }
    });
    if (data.code == '000000') {
        res.json({code: 0,data:captcha});
    } else {

        res.json({code:1,error:'error'});
    } 
});

router.post('/register',async function (req,res) {
    let {email,password,mobile,captcha}=req.body;
    if (req.session.captcha != captcha) {
        return res.json({code:0,error:'验证码错误!'});
    }
    let user=new User({email,password,mobile});
    try {
        await user.save();
        res.json({code:0,data:'注册成功'});
    } catch (error) {
        res.json({code:1,error});
    }
});
router.post('/login',async function (req,res) {
    let {email,password,captcha,type}=req.body;
    if (type=='mobile') {
        if (req.session.captcha != captcha) {
            return res.json({code:0,error:'登录失败!'});
        }
        let mobile=req.session.mobile;
        console.log(mobile,req.session.captcha , captcha);
        let oldUser=await User.findOne({mobile});
        if (oldUser) {
            req.session.user=oldUser;
            res.json({code:0,data:'登录成功'});
        } else {
            res.json({code:1,error:'用户名或密码错误'});
        }
    } else {
        let oldUser=await User.findOne({email,password});
        if (oldUser) {
            req.session.user=oldUser;
            res.json({code:0,data:'登录成功'});
        } else {
            res.json({code:1,error:'用户名或密码错误'});
        }
    }
});
module.exports=router;

4.5 Rooms/index.jsx #

src/pages/Rooms/index.jsx

import React,{Component} from 'react';
export default class Rooms extends Component{
    render(){
        return (
            <div>Rooms</div>
        )    
    }
}

4.6 User/Login.jsx #

src/pages/User/Login.jsx

import React,{Component} from 'react';
import {connect} from 'dva';
import Link from 'umi/link';
import {
    Form,
    Input,
    Button,
    Select,
    Row,
    Col,
    Popover,
    Progress,
    Tabs
} from 'antd';
import styles from './Login.less';
const FormItem=Form.Item;
const {Option}=Select;
const InputGroup=Input.Group;
const TabPane = Tabs.TabPane;

@connect(({register,loading}) => ({register,submitting: loading.effects['register/submit']}))
@Form.create()
export default class extends Component {
    state={
        count: 0,
        type:'account'
    };

    onGetCaptcha=() => {
        const {dispatch,form}=this.props;
        let mobile=form.getFieldValue('mobile');
        dispatch({type: 'register/getCaptcha',payload: mobile});
        let count=59;
        this.setState({count});
        this.interval=setInterval(() => {
            count-=1;
            this.setState({count});
            if (count===0) {
                clearInterval(this.interval);
            }
        },1000);
    };
    handleSubmit=e => {
        e.preventDefault();
        const {form,dispatch}=this.props;
        const {type}=this.state;
        form.validateFields({
            force: true
        },(err,values) => {
            if (!err) {
                dispatch({
                    type: 'login/submit',
                    payload: {
                        ...values,
                        type
                    }
                });
            }
        });
    };
    onSwitch = type => {
        this.setState({
          type,
        });
    };
    render() {
        const {form,submitting}=this.props;
        const {getFieldDecorator}=form;
        const {count}=this.state;
        return (
            <div className={styles.register}>
                <h3>用户注册</h3>
                <Form onSubmit={this.handleSubmit}>
                    <Tabs defaultActiveKey="account" onChange={this.onSwitch}>
                      <TabPane tab="账号密码登录" key="account">
                        <FormItem>
                            {getFieldDecorator('email',{
                                rules: [
                                    {
                                        required: true,
                                        message: '邮箱必须输入'
                                    },{
                                        type: 'email',
                                        message: '邮箱格式不合法'
                                    }
                                ]
                            })(<Input size="large" placeholder="邮箱" />)}
                        </FormItem>
                        <FormItem>
                            {getFieldDecorator('password',{
                                rules: [
                                    {
                                        required: true,
                                        message: '请输入密码!'
                                    }
                                ]
                            })(<Input size="large" type="password" placeholder="密码" />)}
                        </FormItem>
                    </TabPane>
...

4.7 User/Login.less #

src/pages/User/Login.less

.register{
    width:450px;
    border:1px solid #CCC;
    border-radius: 10px;
    padding:20px;
    h3{
        text-align: center;
    }
    .getCaptcha{
        width:100%;
        display:block;
    }
    .toRegister{
        float:right;
    }
}

4.8 User/Register.jsx #

src/pages/User/Register.jsx

import React,{Component} from 'react';
import {connect} from 'dva';
import Link from 'umi/link';
import {
    Form,
    Input,
    Button,
    Select,
    Row,
    Col,
    Popover,
    Progress
} from 'antd';
import styles from './Register.less';
const FormItem=Form.Item;
const {Option}=Select;
const InputGroup=Input.Group;

const passwordStatusMap={
    ok: (
        <div className={styles.success}>
            强度: 强
                </div>
    ),
    pass: (
        <div className={styles.warning}>
            v强度: 中
                </div>
    ),
    poor: (
        <div className={styles.error}>
            强度: 太短
                </div>
    )
};
const passwordProgressMap={
    ok: 'success',
    pass: 'normal',
    poor: 'exception'
};

@connect(({register,loading}) => ({register,submitting: loading.effects['register/submit']}))
@Form.create()
export default class extends Component {
    state={
        count: 0,
        confirmDirty: false,
        visible: false,
        help: '',
        prefix: '86'
    };
    getPasswordStatus=() => {
        const {form}=this.props;
        const value=form.getFieldValue('password');
        if (value&&value.length>9) {
            return 'ok';
        }
        if (value&&value.length>5) {
            return 'pass';
        }
        return 'poor';
    };

    renderPasswordProgress=() => {
        const {form}=this.props;
        const value=form.getFieldValue('password');
        const passwordStatus=this.getPasswordStatus();
        return value&&value.length
            ? (
                <div className={styles[`progress-${passwordStatus}`]}>
                    <Progress
                        status={passwordProgressMap[passwordStatus]}
                        className={styles.progress}
                        strokeWidth={6}
                        percent={value.length*10>100
                            ? 100
                            :value.length*10}
                        showInfo={false} />
                </div>
            )
            :null;
    };

    checkPassword=(rule,value,callback) => {
        const {visible,confirmDirty}=this.state;
        if (!value) {
            this.setState({
                help: "请输入密码!",
                visible: !!value
            });
            callback('error');
        } else {
            this.setState({help: ''});
            if (!visible) {
                this.setState({
                    visible: !!value
                });
            }
            if (value.length<6) {
                callback('error');
            } else {
                const {form}=this.props;
                if (value&&confirmDirty) {
                    form.validateFields(['confirm'],{force: true});
                }
                callback();
            }
        }
    };
    checkConfirm=(rule,value,callback) => {
        const {form}=this.props;
        if (value&&value!==form.getFieldValue('password')) {
            callback('两次输入的密码不匹配!');
        } else {
            callback();
        }
    };
    changePrefix=value => {
        this.setState({prefix: value});
    };
    onGetCaptcha=() => {
        const {dispatch,form}=this.props;
        let mobile=form.getFieldValue('mobile');
        dispatch({type: 'register/getCaptcha',payload: mobile});
        let count=59;
        this.setState({count});
        this.interval=setInterval(() => {
            count-=1;
            this.setState({count});
            if (count===0) {
                clearInterval(this.interval);
            }
        },1000);
    };
    handleSubmit=e => {
        e.preventDefault();
        const {form,dispatch}=this.props;
        form.validateFields({
            force: true
        },(err,values) => {
            console.log('values',values);
            if (!err) {
                const {prefix}=this.state;
                dispatch({
                    type: 'register/submit',
                    payload: {
                        ...values,
                        prefix
                    }
                });
            }
        });
    };
    render() {
        const {form,submitting}=this.props;
        const {getFieldDecorator}=form;
        const {count,prefix,help,visible}=this.state;
        return (
            <div className={styles.register}>
                <h3>用户注册</h3>
                <Form onSubmit={this.handleSubmit}>
                    <FormItem>
                        {getFieldDecorator('email',{
                            rules: [
                                {
                                    required: true,
                                    message: '邮箱必须输入'
                                },{
                                    type: 'email',
                                    message: '邮箱格式不合法'
                                }
                            ]
                        })(<Input size="large" placeholder="邮箱" />)}
                    </FormItem>
                    <FormItem help={help}>
                        <Popover
                            content={
                                <div style={{padding: '4px 0'}}>
                                    {passwordStatusMap[this.getPasswordStatus()]}
                                    {this.renderPasswordProgress()}
                                    <div style={{marginTop: 10}}>
                                      请至少输入 6 个字符。请不要使用容易被猜到的密码。
                                    </div>
                                </div>
                            }
                            overlayStyle={{width: 240}}
                            placement="right"
                            visible={visible}
                        >
                            {getFieldDecorator('password',{
                                rules: [
                                    {
                                        validator: this.checkPassword
                                    }
                                ]
                            })(<Input size="large" type="password" placeholder='至少六位密码,区分大小写' />)}
                        </Popover>
                    </FormItem>
                    <FormItem>
                        {getFieldDecorator('confirm',{
                            rules: [
                                {
                                    required: true,
                                    message: '请输入确认密码!'
                                },{
                                    validator: this.checkConfirm
                                }
                            ]
                        })(<Input size="large" type="password" placeholder="确认密码" />)}
                    </FormItem>
                    <FormItem>
                        <InputGroup compact>
                            <Select
                                size="large"
                                value={prefix}
                                onChange={this.changePrefix}
                                style={{
                                    width: '20%'
                                }}>
                                <Option value="86">+86</Option>
                                <Option value="87">+87</Option>
                            </Select>
                            {getFieldDecorator('mobile',{
                                rules: [
                                    {
                                        required: true,
                                        message: '请输入手机号!'
                                    },{
                                        pattern: /^\d{11}$/,
                                        message: '手机号格式错误!'
                                    }
                                ]
                            })(<Input
                                size="large"
                                style={{
                                    width: '80%'
                                }}
                                placeholder='手机号' />)}
                        </InputGroup>
                    </FormItem>
                    <FormItem>
                        <Row gutter={8}>
                            <Col span={16}>
                                {getFieldDecorator('captcha',{
                                    rules: [
                                        {
                                            required: true,
                                            message: '请输入验证码!'
                                        }
                                    ]
                                })(<Input size="large" placeholder="验证码" />)}
                            </Col>
                            <Col span={8}>
                                <Button
                                    size="large"
                                    disabled={count}
                                    className={styles.getCaptcha}
                                    onClick={this.onGetCaptcha}>
                                    {count
                                        ? `${count} s`
                                        :'获取验证码'}
                                </Button>
                            </Col>
                        </Row>
                    </FormItem>
                    <FormItem>
                        <Button
                            size="large"
                            loading={submitting}
                            className={styles.submit}
                            type="primary"
                            htmlType="submit">
                            注册
                                                </Button>
                        <Link className={styles.login} to="/User/Login">
                            使用已有账户登录
                                                </Link>
                    </FormItem>
                </Form>
            </div>

        );
    }
}

4.9 models/login.js #

src/pages/User/models/login.js

import * as loginService from '../services/login';
import router from 'umi/router';
export default {
  namespace: 'login',

  state: {

  },

  effects: {
      *getCaptcha({payload},{call,put}) {
         yield call(loginService.getCaptcha,payload);
    },
    *submit({payload},{call,put}) {
      let {code}=yield call(loginService.submit,payload);
      if(code == 0)
        router.push('/Rooms');
    }
  },

  reducers: {

  },
};

4.10 models/register.js #

src/pages/User/models/register.js

import * as registerService from '../services/register';
import router from 'umi/router';
export default {
  namespace: 'register',

  state: {

  },

  effects: {
      *getCaptcha({payload},{call,put}) {
         yield call(registerService.getCaptcha,payload);
    },
    *submit({payload},{call,put}) {
      yield call(registerService.submit,payload);
      router.push('/User/Login');
    }
  },

  reducers: {

  },
};

4.11 services/login.js #

src/pages/User/services/login.js

import request from '../../../utils/request';

export function getCaptcha(mobile) {
  return request(`/user/getCaptcha?mobile=${mobile}`);
}

export function submit(values) {
  return request(`/user/login`,{
    method: 'POST',
    body: JSON.stringify(values),
    headers: {
      'Content-Type': "application/json",
      "Accept":"application/json'"
    }
  });
}

4.12 services/register.js #

src/pages/User/services/register.js

export function submit(values) {
  return request(`/user/register`,{
    method: 'POST',
    body: JSON.stringify(values),
    headers: {
      'Content-Type': "application/json",
      "Accept":"application/json'"
    }
  });
}

4.13 utils/request.js #

src/utils/request.js

-  return fetch(HOST+url, options)
+  return fetch(HOST+url,{
+    ...options,
+    credentials:'include'
+  })

5. 实现房间功能 #

5.1 server/app.js #

server/app.js

+ let room=require('./routes/room');
app.use('/user',user);
+ app.use('/room',room);

5.2 models/index.js #

server/models/index.js

+
+const Room = conn.model('Room', {
+    name: {type: String,required: true}
+});
+
 module.exports={
-    User
+    User,
+    Room
 }

5.3 routes/room.js #

server/routes/room.js

let express=require('express');
let router=express.Router();
const {Room}=require('../models');

router.get('/list',async function (req,res) {
    let list = await Room.find();
    res.json({
        code: 0,
        data:list
    });
});

router.post('/create',async function (req,res) {
    let {name}=req.body;
    await Room.create({name});
    res.json({
        code: 0,
        data:'房间创建成功'
    });
});

module.exports=router;

5.4 Room/index.jsx #

src/pages/Room/index.jsx

import React,{Component} from 'react';
import Link from 'umi/link';
import {connect} from 'dva';
import {Layout,Menu,Card,Input,Row,Col,Button,Form} from 'antd';
import styles from './index.less';
const {Header,Footer,Content}=Layout;

@connect(({room,loading}) => ({room,loading: loading.effects['room/list']}))
@Form.create()
export default class Rooms extends Component {
    createRoom=() => {
        let name=this.props.form.getFieldValue('keyword');
        if (name) {
            this.props.dispatch({
                type: 'room/create',
                payload:{name}
            });
        }
    }
    render() {
        let name=this.props.form.getFieldValue('keyword');
        let rooms=this.props.room.list;
        if (name) {
            rooms = rooms.filter(item => item.name.indexOf(name)!=-1);
        }

        return (
            <Layout>
                <Header>
                    <a className={styles.logo}>前端聊天室</a>
                    <Menu
                        theme="dark"
                        mode="horizontal"
                        defaultSelectedKeys={['register']}
                        style={{ lineHeight: '64px' }}
                    >
                        <Menu.Item key="register">注册</Menu.Item>
                        <Menu.Item key="login">登录</Menu.Item>
                    </Menu>
                </Header>
                <Content>
                    <Card>
                        <Row gutter={16}>
                            <Col span={6} offset={8}>
                                {
                                    this.props.form.getFieldDecorator('keyword')(
                                        <Input type="text" placeholder="请输入房间名"></Input>
                                    )
                                }
                            </Col>
                        </Row>                
                    </Card>
                    <Card>
                        <Row gutter={16}>
                            {
                                rooms.length>0?rooms.map(item => (
                                    <Col span={6} key={item._id}>
                                        <Card
                                            title={item.name}
                                            extra={<Link to={`/Rooms/${item._id}`}>进入</Link>}
                                        >
                                            <p>用户1</p>
                                            <p>用户2</p>
                                            <p>用户3</p>
                                        </Card>
                                    </Col>    
                                )):<Button onClick={this.createRoom}>创建房间</Button>
                            }
                        </Row>
                    </Card>
                </Content>
                <Footer>
                    <div className={styles.copyright}> ©2018 前端培训</div>
                </Footer>
            </Layout>
        )
    }
}

5.5 Room/index.less #

src/pages/Room/index.less

.logo{
    width:120px;
    height:64px;
    line-height: 64px;
    float:left;
}

.copyright{
    text-align:center;
}

5.6 models/room.js #

src/pages/Room/models/room.js

import * as roomService from '../services/room';
export default {

  namespace: 'room',

  state: {
    list: []
  },

  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(({pathname,query}) => {
        if(pathname == '/Room'){
          dispatch({
            type:'getRooms'
          });
        }
      });
    },
  },

  effects: {
    *getRooms({ payload }, { call, put }) {
      let {data: list}=yield call(roomService.getRooms);
      yield put({type: 'save',payload: {list}});
    },
    *create({ payload }, { call, put }) {
      yield call(roomService.create,payload);
      yield put({type: 'getRooms'});
    }
  },

  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
  },
};

5.7 services/room.js #

src/pages/Room/services/room.js

import request from '../../../utils/request';

export function getRooms() {
  return request(`/room/list`);
}
export function create(values) {
  return request(`/room/create`,{
    method: 'POST',
    body: JSON.stringify(values),
    headers: {
      'Content-Type': "application/json",
      "Accept":"application/json'"
    }
  });
}

6. 实现聊天功能 #

6.1 server/app.js #

server/app.js

let express=require('express');
let session=require('express-session');
const cors=require('cors');
const bodyParser=require('body-parser');
const {User,Room,Message}=require('./models');
let app=express();
app.use(cors({
    origin:'http://localhost:8000',
    credentials:true
}));
app.use(bodyParser.json());
app.use(session({
    resave:true,
    secret:'zfpx',
    saveUninitialized:true
}));
let user=require('./routes/user');
let room=require('./routes/room');
app.use('/user',user);
app.use('/room',room);
let server=require('http').createServer(app);
server.listen(3000);
let io=require('socket.io')(server);
io.on('connection',async function (socket) {
    let userId=socket.handshake.query.user;//用户
    let user=await User.findById(userId);
    let roomId=socket.handshake.query.room; //房间
    //查找房间
    let room=await Room.findById(roomId);
    socket.join(roomId);//把当前socket添加到房间里
    //把用户放到房间里
    await User.findByIdAndUpdate(userId,{room: roomId});
    socket.on('getRoom',async function () {
        //查找房间中的用户
        let users=await User.find({room: roomId});
        //查找房间里的消息
        let messages = await Message.find({room: roomId}); 
        socket.emit('room',{room,users,messages});
    });
    socket.on('message',async function (content) {
        let {_id} = await Message.create({
            user:userId,
            content,
            room:roomId
        });
        let message=await Message.findById(_id).populate('user').populate('room').exec();
        io.in(roomId).emit('message',message);
    });

});

6.2 models/index.js #

server/models/index.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
const {DB} = require('../config');
const conn = mongoose.createConnection(DB.url,{ useNewUrlParser: true });
const User = conn.model('User', {
    email: {type: String,required: true},        //邮箱 
    password: {type: String,required: true},     //密码
    mobile: {type: String,required: true},       //手机号
    avatar: {type: String,required: false},      //头像
    createAt: {type: Date,default: Date.now},    //创建时间 
    room:{type:ObjectId,ref:'Room'}              //所属房间
});

const Room = conn.model('Room', {
    name: {type: String,required: true},         //房间名
    avatar: {type: String},                      //房间图片
    createAt:{type:Date,default:Date.now}        //创建时间
});

const Message = conn.model('Message', {
    content: {type: String},                     //内容
    user: {type: ObjectId,ref: 'User'},          //用户
    createAt: {type: Date,default: Date.now},    //创建时间 
    room:{type:ObjectId,ref:'Room'}              //房间
});


module.exports={
    User,
    Room,
    Message
}

6.3 routes/user.js #

server/routes/user.js

+ const gravatar=require('gravatar');
-    let user=new User({email,password,mobile});
+    let user=new User({email,password,mobile,avatar});
-            res.json({code:0,data:'登录成功'});
+            res.json({code:0,data:oldUser});
-            res.json({code:0,data:'登录成功'});
+            res.json({code:0,data:oldUser});

6.4 Room/$id.jsx #

src/pages/Room/$id.jsx

import React,{Component} from 'react';
import {connect} from 'dva';
import {Layout,Menu,Card,Input,Row,Col,Button,Form,List,Avatar} from 'antd';
const {Header,Sider,Footer,Content}=Layout;
@connect(({chat}) => ({chat}))
@Form.create()    
export default class Chat extends Component {
    talk=() => {
        let content=this.props.form.getFieldValue('content');
        this.props.dispatch({
            type: 'chat/talk',
            payload:content
        });
    }
    render() {
        let {users,messages}=this.props.chat;
        return (
            <Layout>
                <Header><a href="#">前端聊天室</a></Header>
                <Layout>
                    <Sider>
                        <Menu
                            theme="dark"
                            mode="vertical"
                            defaultSelectedKeys={[]}
                        >
                            {
                                users.map(user => (<Menu.Item key={user._id}>{user.email}</Menu.Item>))
                            }
                        </Menu>
                    </Sider>
                    <Content style={{height: '600px'}}>
                        <Card>
                            <List
                                itemLayout="horizontal"
                                dataSource={messages}
                                renderItem={item => (
                                <List.Item>
                                    <List.Item.Meta
                                        avatar={<Avatar src={item.user.avatar} />}
                                        title={item.user.email}
                                        description={item.content}
                                    />
                                </List.Item>
                            )}
                         />
                        </Card>
                        <Card>
                            <Row gutter={8}>
                                <Col span={23}>
                                    {
                                        this.props.form.getFieldDecorator('content')(<Input/>)
                                    }
                                </Col>
                                <Col span={1}><Button onClick={this.talk}>发言</Button></Col>
                            </Row>
                        </Card>
                    </Content>
                </Layout>
                <Footer style={{textAlign:'center'}}>
                        @2018 前端培训
                </Footer>
            </Layout>
        )
    }
}

6.5 Room/index.jsx #

src/pages/Room/index.jsx

-     extra={<Link to={`/Rooms/${item._id}`}>进入</Link>}
+     extra={<Link to={`/Room/${item._id}`}>进入</Link>}

6.6 models/chat.js #

src/pages/Room/models/chat.js

import IO from 'socket.io-client';
import router from 'umi/router';
export default {
  namespace: 'chat',

  state: {
    room: {},
    users: [],
    messages:[]
  },

  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(({pathname,query}) => {
        let regexp=/\/Room\/([^/]+)$/;
        let result=pathname.match(regexp);
        if (result) {
          let roomId=result[1];
          let userStr = window.sessionStorage.getItem('user');
          if (userStr) {
            let user=JSON.parse(userStr);
            const socket= window.socket = new IO('http://localhost:3000',{query:{user:user._id,room:roomId}});
            socket.on('room',function ({room,users,messages}) {
                dispatch({type: 'save',payload: {room,users,messages}});
            });
            socket.emit('getRoom');
            socket.on('message',function (message) {
              console.log(message);
              dispatch({type: 'messageAdded',payload:message});
            });
          } else {
            router.push('/User/Login');
          }
        } 
      });
    },
  },
  effects: {
    *talk({payload},{call,put,select}) {
      window.socket.emit('message',payload);
   }
  },
  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
    messageAdded(state,{payload}) {
      return {...state, messages:[...state.messages,payload] };
    }
  }
};

6.7 models/room.js #

src/pages/Room/models/room.js

-    dispatch({
-       type:'getRooms'
-    });
+    dispatch({type:'getRooms'});

6.8 models/login.js #

src/pages/User/models/login.js

-      let {code}=yield call(loginService.submit,payload);
-      if(code == 0)
-        router.push('/Rooms');
+      let {code,data}=yield call(loginService.submit,payload);
+      if (code==0) {
+        yield put({type: 'save',payload: {user: data}});
+        window.sessionStorage.setItem('user',JSON.stringify(data));
+        router.push('/Room');
+      }

7. 上传图片 #

7.1 server/app.js #

server/app.js

-app.use(bodyParser.json());
+app.use(bodyParser.json({limit:'1000kb'}));

-        let messages = await Message.find({room: roomId}); 
-        console.log(room,users,messages);
+        let messages = await Message.find({room: roomId}).populate('user'); 

7.2 models/index.js #

server/models/index.js

-    createAt:{type:Date,default:Date.now}        //创建时间
+    createAt: {type: Date,default: Date.now},        //创建时间
+    users:[{type:ObjectId,ref:'User'}]

7.3 routes/room.js #

server/routes/room.js

-const {Room}=require('../models');
+const {Room,User}=require('../models');

-    let list = await Room.find();
+    let list=await Room.find();
+    for (let i=0;i<list.length;i++){
+        let users=await User.find({room: list[i]._id});
+        console.log(users);
+        list[i].users=users;
+    }
+    console.log(list);

7.4 routes/user.js #

server/routes/user.js

+ router.post('/changeAvatar',async function (req,res) {
+     let {userId,avatar}=req.body;
+     try {
+         let user = await User.findById(userId);
+         user.avatar=avatar;
+         await user.save();
+         res.json({code:0,data:user});
+     } catch (error) {
+         res.json({code:1,error});
+     }
+ });

7.5 Room/index.jsx #

src/pages/Room/index.jsx

import React,{Component} from 'react';
import Link from 'umi/link';
import {connect} from 'dva';
import Cropper from 'react-cropper';
import 'cropperjs/dist/cropper.css'; 
import {Layout,Menu,Card,Input,Row,Col,Button,Form,List,Avatar,Modal} from 'antd';
import styles from './index.less';
const {Header,Footer,Content}=Layout;

@connect(({room,login,loading}) => ({room,login}))
@Form.create()
export default class Rooms extends Component {
    state={
        src: '',
        cropperVisible: false
    }
    createRoom=() => {
        let name=this.props.form.getFieldValue('keyword');
        if (name) {
            this.props.dispatch({
                type: 'room/create',
                payload:{name}
            });
        }
    }
    //上传图片
    changeAvatar=(userId) => {
        let that=this;
        const $input=document.createElement('input');
        $input.style.display = 'none';
        $input.setAttribute('type', 'file');
        $input.setAttribute('accept','*/*');
        $input.onchange=(e) => {
            const file=e.target.files[0];
            if (!file) return;
            const reader=new FileReader();
            reader.onloadend=function () {
                let avatar=this.result;
                that.setState({
                    src: avatar,
                    cropperVisible:true
                });

            }
            reader.readAsDataURL(file);
        }
        $input.click();
    }
    confirmChange=(userId) => {
        let avatar = this.cropper.getCroppedCanvas().toDataURL();
        this.setState({cropperVisible:false});
        this.props.dispatch({
               type: 'login/changeAvatar',
               payload: {userId,avatar}
        }); 
    }
    render() {
        let name=this.props.form.getFieldValue('keyword');
        let rooms=this.props.room.list;
        if (name) {
            rooms = rooms.filter(item => item.name.indexOf(name)!=-1);
        }

        return (
            <Layout>
                <Header>
                    <Row>
                        <Col span={2}><a className={styles.logo}>前端聊天室</a></Col>
                        <Col span={21}><Menu
                        theme="dark"
                        mode="horizontal"
                        defaultSelectedKeys={['register']}
                        style={{ lineHeight: '64px' }}
                        >
                        <Menu.Item key="register">注册</Menu.Item>
                        <Menu.Item key="login">登录</Menu.Item>
                        </Menu></Col>
                        <Col span={1}>{this.props.login.user&&<Avatar onClick={()=>this.changeAvatar(this.props.login.user._id)} src={this.props.login.user.avatar}/>}</Col>
                    </Row>
                </Header>
                <Content>
                    <Card>
                        <Row gutter={16}>
                            <Col span={6} offset={8}>
                                {
                                    this.props.form.getFieldDecorator('keyword')(
                                        <Input type="text" placeholder="请输入房间名"></Input>
                                    )
                                }
                            </Col>
                        </Row>                
                    </Card>
                    <Card>
                        <Row gutter={16}>
                            {
                                rooms.length>0?rooms.map(item => (
                                    <Col span={6} key={item._id}>
                                        <Card
                                            title={item.name}
                                            extra={<Link to={`/Room/${item._id}`}>进入</Link>}
                                        >
                                            {
                                                item.users.map(user => <Avatar style={{marginLeft:5}} src={user.avatar} />)
                                            }
                                        </Card>
                                    </Col>    
                                )):<Button onClick={this.createRoom}>创建房间</Button>
                            }
                        </Row>
                    </Card>
                </Content>
                <Footer>
                    <div className={styles.copyright}> ©2018 前端培训</div>
                </Footer>
                <Modal
                    onOk={()=>this.confirmChange(this.props.login.user._id)}
                    onCancel={()=>this.setState({cropperVisible:false})}
                    visible={this.state.cropperVisible} >
                    <Cropper
                            ref={i=>this.cropper =i}
                            src={this.state.src}
                            style={{height: 400, width: 400}}
                            aspectRatio={16 / 9}
                        guides={false} />
                </Modal>
            </Layout>
        )
    }
}

7.6 /models/chat.js #

src/pages/Room/models/chat.js

+ dispatch({type: 'login/save',payload: {user}});

7.7 models/room.js #

src/pages/Room/models/room.js

-        if(pathname == '/Room'){
-          dispatch({type:'getRooms'});
+        if (pathname=='/Room') {
+          let userStr = window.sessionStorage.getItem('user');
+          if (userStr) {
+            let user=JSON.parse(userStr);
+            dispatch({type: 'login/save',payload: {user}});
+            dispatch({type:'getRooms'});
+          } else {
+            router.push('/User/Login');
+          }

7.8 models/login.js #

src/pages/User/models/login.js

+    *changeAvatar({payload},{call,put}) {
+      let {code,data}=yield call(loginService.changeAvatar,payload);
+      if (code==0) {
+        window.sessionStorage.setItem('user',JSON.stringify(data));
+        yield put({type: 'save',payload: {user: data}});
+      }

7.9 services/login.js #

src/pages/User/services/login.js

export function changeAvatar(values) {
  return request(`/user/changeAvatar`,{
    method: 'POST',
    body: JSON.stringify(values),
    headers: {
      'Content-Type': "application/json",
      "Accept":"application/json'"
    }
  });
}

参考链接 #

  • zhufeng-dva-chat
  • ant.design
  • pro.ant.design
  • umijs
  • ucpaas

访问验证

请输入访问令牌

Token不正确,请重新输入