一步步搭建react后台系统4

一步一步构件react后台系统4 之注册页面

添加注册页面

  1. 添加页面
  • views/register/index
    这里依然采用ant里面的Form注册组件
    其他新增的或修改的会打上标记, 没打标记的, 都是ant的form注册组件
import React from 'react'   // +
import { Form, Input, Tooltip, Icon, Cascader, Select, Row, Col, Checkbox, Button, AutoComplete } from 'antd';
import { validatorPhone } from '@/utils/index'  // +
import './index.css'  // +
const FormItem = Form.Item;
const Option = Select.Option;
const AutoCompleteOption = AutoComplete.Option;

class RegistrationForm extends React.Component {
    state = {
        confirmDirty: false,
        autoCompleteResult: []
    };

    handleSubmit = (e) => {
        e.preventDefault();
        this.props.form.validateFieldsAndScroll((err, values) => {
            if (!err) {
                console.log('Received values of form: ', values);
            }
        });
    }

    handleConfirmBlur = (e) => {
        const value = e.target.value;
        this.setState({ confirmDirty: this.state.confirmDirty || !!value });
    }

    compareToFirstPassword = (rule, value, callback) => {
        const form = this.props.form;
        if (value && value !== form.getFieldValue('password')) {
            callback('Two passwords that you enter is inconsistent!');
        } else {
            callback();
        }
    }

    validateToNextPassword = (rule, value, callback) => {
        const form = this.props.form;
        if (value && this.state.confirmDirty) {
            form.validateFields(['confirm'], { force: true });
        }
        callback();
    }

    handleWebsiteChange = (value) => {
        let autoCompleteResult;
        if (!value) {
            autoCompleteResult = [];
        } else {
            autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`);
        }
        this.setState({ autoCompleteResult });
    }
    // 获取验证码, 传递给父组件
    getCaptcha = (e) => {  // +
        var num = Math.floor(Math.random()*10000)
        this.props.handleCaptcha(num)
    }

    render() {
        const { getFieldDecorator } = this.props.form;
        const { autoCompleteResult } = this.state;
        const formItemLayout = {
            labelCol: {
                xs: { span: 24 },
                sm: { span: 8 },
            },
            wrapperCol: {
                xs: { span: 24 },
                sm: { span: 16 },
            },
        };
        const tailFormItemLayout = {
            wrapperCol: {
                xs: {
                    span: 24,
                    offset: 0,
                },
                sm: {
                    span: 16,
                    offset: 8,
                },
            },
        };
        const prefixSelector = getFieldDecorator('prefix', {
            initialValue: '86',
        })(
            
        );

        const websiteOptions = autoCompleteResult.map(website => (
            {website}
        ));
        // +- 里面的组件都修改了。
        return (
            
{getFieldDecorator('username', { rules: [{ message: '请输入用户名称', }, { required: true, message: '用户名称必填', }], })( )} {getFieldDecorator('password', { rules: [{ required: true, message: '请输入用户密码!', }, { validator: this.validateToNextPassword, }], })( )} {getFieldDecorator('confirm', { rules: [{ required: true, message: '请验证密码', }, { validator: this.compareToFirstPassword, }], })( )} {getFieldDecorator('phone', { rules: [{ required: true, message: '请输入正确的手机号码' }, {validator: validatorPhone.bind(this)()} // + 添加了验证手机正则 ], })( )} {getFieldDecorator('captcha', { rules: [{ required: true, message: '请输入正确的验证码' }], })( // + 只读 )} // +- 添加验证码事件 电子邮箱 )} > {getFieldDecorator('email', { rules: [{ required: false, message: '请输入电子邮箱', whitespace: true }], })( )}
); } } // 当input发生改变时, 调用父传递过来的事件。 const onFieldsChange = (props, changeFields) => { props.onChange(changeFields) } // 接受到值, 并注入Form组件内 const mapPropsToFields = props => { let captcha = props.captcha captcha && captcha.value && captcha.errors && delete captcha.errors // 当存在值的时候, 删除errors return { username: Form.createFormField({ ...props.username, }), password: Form.createFormField({ ...props.password }), confirm: Form.createFormField({ ...props.confirm }), phone: Form.createFormField({ ...props.phone }), captcha: Form.createFormField({ ...props.captcha }), email: Form.createFormField({ ...props.email }) } } // value改变事件 const onValuesChange = (_, values) => { } const WrappedRegistrationForm = Form.create({onFieldsChange, mapPropsToFields, onValuesChange})(RegistrationForm); // 传递参数给form组件 class Register extends React.Component { state = { fields: { username: { value: '我是useranme默认值' } } } // 当input的value发生改变, 就改变值, handleFormChange = (changedFields) => { this.setState(({fields}) => { return { fields: {...fields, ...changedFields} } }) } // 点击获取验证码事件。 handleCaptcha = (captchaValue) => { this.setState(({fields}) => { let captcha = { // 合并captcha对象, 修改value ...((typeof fields.captcha === 'object') ? fields.captcha : {}), value: captchaValue } return { fields: {...fields, captcha} } }) } render () { let fields = this.state.fields return (
) } } export default Register
  • views/register/index.css
.register {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-pack: center;
    justify-content: center;
    -ms-flex-align: center;
    align-items: center;
    height: 100%;
    background: #f3f3f3;
}
.register>.register-form {
    width: 60%;
    height: auto;
    padding: 36px;
    -webkit-box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
    box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
    background: #fff;
}

#root{
    height: 100%;
}
.App{
    height: 100%;
}

  • utils/index
export const validatorPhone = function () {
    return (rule, value, callback) => {
        const form = this.props.form;
        if (value && !(/^1[3|4|5|8][0-9]\d{4,8}$/.test(form.getFieldValue('phone'))) ) {
            callback('请输入正确的手机号码');  还没有账号? 去注册
        } else {
            callback();
        }
    }
}
  • views/login/index.js

添加一个注册入口

  还没有账号? 去注册
  • router/index.js

别忘了, 添加到路由配置里面
注意: 这里添加了一个meta对象, 用来自定义一些参数。
@params: isAuth 是否不需要验证(是否登录)

import React from 'react'
import Login from '@/views/login/index'
import Index from '@/views/index/index'
import Register from '@/views/register/index' // +
import { RenderRoutes } from '@/router/utils'
const Ui = ({routes}) => (

Ui

) const Button = () =>

Button

const Icon = () =>

Icon

const Animation = () =>

Animation

const From = () =>

From

export const menus = [ // 菜单相关路由 { path: '/index/UI', name: 'UI', icon:'video-camera', component: Ui , routes: [ {path: '/index/UI/button', name: '按钮', icon: 'video-camera', component: Button }, {path: '/index/UI/Icon', name: '图标', icon: 'video-camera', component: Icon } ] }, { path: '/index/animation', name: '动画', icon: 'video-camera', component: Animation }, { path: '/index/form', name: '表格', icon: 'video-camera', component: From }, ] // isAuth 表示不用验证是否登录 export const main = [ { path: '/login', exact: true, name: '登录', component: Login, meta: { // +- isAuth: true } }, { path: '/register', exact: true, name: '注册', component: Register, meta: { // + isAuth: true } }, { path: '/', exact: true, name: '首页', Redirect: '/index'}, { path: '/index', name: '首页', component: Index, routes: menus } ] export const routerConfig = { main, menus }
  • router/utils.js

修改渲染组件,
当路径是 不需要验证是否登录的页面 的时候, 就不进行判断是否重定向到登录页面了。

// 渲染当前组件
export const RouteWithSubRoutes = route => ({
           var isAuthenticated  = sessionStorage.getItem('isAuthenticated')
            if ( !(typeof route.meta === 'object' && route.meta.isAuth) && !isAuthenticated ) {  // +-
                return 
            }
            return (
                route &&( route.Redirect ? () :
                ())
            )
        }}
    />
);

就这样, 页面做好了。

查看github代码
切换Log 到
添加注册页面, 修改路由配置, 修改路由渲染组件, 修改重定向登录规则
即可看到当前代码。

  1. 添加后台接口

注册页面做好了, 但是需要后台接口, 这里添加后台接口。
先打开后台项目。

修改package.json 添加打开项目快捷方式

"nodemon": "nodemon bin/www"  // +

然后运行项目

npm run nodemon
  • 修改routes/index.js

// 添加接口

const utils = require ('../utils/index')
let { isEmpty }  = utils

router.post('/register', async (ctx, next) => {
    let body = ctx.request.body
    let username = body.username;
    let password = body.password;
    let captcha = body.captcha;
    let phone = body.phone;
    console.log(body)
    if (isEmpty(username)) {
        ctx.body = {
            code: 0,
            message: "用户名不能为空",
        }
    }
    else if (isEmpty(password)) {
        ctx.body = {
            code: 0,
            message: "密码不能为空",
        }
    }
    else if (isEmpty(captcha)) {
        ctx.body = {
            code: 0,
            message: "验证码不能为空",
        }
    }
    else if (isEmpty(phone)) {
        ctx.body = {
            code: 0,
            message: "手机号码不能为空",
        }
    } else {
        ctx.body = {
            code: 200,
            message: "注册成功",
        }
    }
})
  • /utils/index.js

新增工具

const isEmpty = (text) => {
    if (text) {
        return false
    }
    return true
}
module.exports = {
    isEmpty
}

好了 后台接口做好了。

  1. 添加接口请求
  • views/register/index
    这里把 connect 的参数都放到connect里面, 因为(每次都需要引用action ,太过繁琐,放到一个页面统一管理也很方便)
    this.props.hanleRegister(values) 执行传递过来的接口事件
import {connect} from "react-redux";  // +
import { mapReigster } from '@/reducer/connect.js' // +
handleSubmit = (e) => {
        e.preventDefault();
        this.props.form.validateFieldsAndScroll((err, values) => {
            if (!err) {
                console.log('Received values of form: ', values);
                this.props.hanleRegister(values)  // +
            }
        });
    }


const WrappedRegistrationForm = connect( mapReigster.mapStateToProps, mapReigster.mapDispatchToProps )(Form.create({onFieldsChange, mapPropsToFields, onValuesChange})(RegistrationForm));  // +-
  • reducer/connect.js

这里把创建action创建函数都封装起来import { receive } from '@/reducer/actionCreate.js'
封装一个fetchPosts事件, 用来请求数据并切换数据。
添加 mapReigster
添加 mapLogin (这里顺便把登录里面的connect参数也提取出来了)

import { action_slidecollapsed, routerConfig, action_login } from '@/reducer/action.js'
import http from '@/api/http.js'  // +
import { receive } from '@/reducer/actionCreate.js' // +
import { ACTION_LOGIN, ACTION_REGISTER } from '@/reducer/action.js' // +

export const mapStateToProps = (state) => {
    console.log(state)
    return {slidecollapsed:  state.slidecollapsed,
        isSlide: false,
    }
}
export const mapDispatchToProps = (dispatch) => {
    return {onSlidecollapsed: () => dispatch(action_slidecollapsed), getRouterConfig: () => {
            return dispatch(routerConfig)
        }, toggleSlide: () => {
        dispatch({type: action_slidecollapsed.type})
    }}
}

export const crumbsMap = {
    mapStateToProps (state) {
        return { routerConfig: state.routerConfig }
    },
    mapDispatchToProps (dispatch) {
        return {getRouterConfig: () => {
                return dispatch(routerConfig)
            }}
    }
}
@params: url 接口路径
@params: actionType action.type
@params: subreddit 数据名称
@params: data 数据

function fetchPosts(url, actionType, subreddit, data) { // +
    return dispatch => {
        dispatch(receive(actionType, subreddit, '暂无数据'))
        return http.post(url, data)
            .then(res => {
                dispatch(receive(actionType, subreddit, res))
            })
    }
}
// 注册
export const mapReigster = {
    mapStateToProps (state) {
        return state.getReigster || state
    },
    mapDispatchToProps (dispatch) {
        return {hanleRegister: (data) => {
                return dispatch(fetchPosts('/register', ACTION_REGISTER,'reigsterData', data))
            }}
    }
}
// 登录
export const mapLogin = {
    mapStateToProps (state) {
        return state.getLogin
    },
    mapDispatchToProps (dispatch) {
        return {handleLogin: (data) => {
                return dispatch(fetchPosts('/login', ACTION_LOGIN, 'loginData', data))
            }}
    }
}


 
 
  • reducer/actionCreate.js
import { ACTION_LOGIN, ACTION_REGISTER } from '@/reducer/action.js'

export const receiveLogin = (dataName, data) => {
    return {
        type: ACTION_LOGIN,
        [dataName]: data
    }
}
// 封装个通用的 actionCreate
export const receive = ( typeName, dataName, data) => {
    return {
        type: typeName,
        [dataName]: data
    }
}
  • reducer/action.js

添加action

export const SLIDECOLLAPSED = 'slidecollapsed'
export const ROUTERCONFIG = 'routerConfig'
export const ACTION_LOGIN = 'getLogin'
export const ACTION_REGISTER = 'ACTION_REGISTER'  // +
export const action_slidecollapsed = {type: SLIDECOLLAPSED}
export const routerConfig = { type: ROUTERCONFIG }
export const action_login = { type: ACTION_LOGIN }
export const action_register = { type: ACTION_REGISTER } // +
  • api/http.js

修改http.js, 添加响应提示

import axios from 'axios'
import { message } from 'antd';
let loadingInstance = {
    close: () =>{}
}
// process.env.NODE_ENV === 'production' ? 'http://123.207.49.214:8028' : 'http://123.207.49.214:8028'
// 创建axios实例
const service = axios.create({
    baseURL: "http://localhost:4000", // api的base_url
    timeout: 5000, // 请求超时时间
    //设置默认请求头,使post请求发送的是formdata格式数据// axios的header默认的Content-Type好像是'application/json;charset=UTF-8',我的项目都是用json格式传输,如果需要更改的话,可以用这种方式修改
    // headers: {
    // "Content-Type": "application/x-www-form-urlencoded"
    // },
    // withCredentials: true, // 允许携带cookie
})
function cloneLoading () {
    loadingInstance.close()
}

// request拦截器
service.interceptors.request.use(config => {
    return config
}, error => {
    cloneLoading()
    // Do something with request error
    Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
    response => {
        cloneLoading()
        if (response.data && response.data.code === 0) {
            message.error(response.data.message, 1.5)
        } else if (response.data && response.data.code === 200) {
            message.success(response.data.message, 1.5)
        }
        return response.data
    }, error => {
        console.log('err' + error)// for debug
        cloneLoading()
        if (error && error.response) {
            switch (error.response.status) {
                case 400:
                    error.desc = '请求错误'
                    break;
                case 401:
                    error.desc = '未授权,请登录'
                    break;
                case 403:
                    error.desc = '拒绝访问'
                    break;
                case 404:
                    error.desc = `请求地址出错: ${error.response.config.url}`
                    break;
                case 408:
                    error.desc = '请求超时'
                    break;
                case 500:
                    error.desc = '服务器内部错误'
                    break;
                case 501:
                    error.desc = '服务未实现'
                    break;
                case 502:
                    error.desc = '网关错误'
                    break;
                case 503:
                    error.desc = '服务不可用'
                    break;
                case 504:
                    error.desc = '网关超时'
                    break;
                case 505:
                    error.desc = 'HTTP版本不受支持'
                    break;
            }
            message.error(error.desc)
        }
        return Promise.reject(error)
    })

export default service


  • views/login/index.js
    既然提取了login的 connect参数, 这里就要修改 login页面
    删除 fetchPosts, loginMap
import { mapLogin } from '@/reducer/connect.js'


export default connect(mapLogin.mapStateToProps, mapLogin.mapDispatchToProps)(Form.create()(Login)); // +
function fetchPosts(subreddit, data) { // -
    return dispatch => {
        dispatch(receiveLogin(subreddit, '暂无数据'))
        return http.post(`/login`, data)
            .then(res => {
                dispatch(receiveLogin(subreddit, res))
            })
    }
}
export const loginMap = { // -
    mapStateToProps (state) {
        return state.getLogin
    },
    mapDispatchToProps (dispatch) {
        return {handleLogin: (data) => {
            return dispatch(fetchPosts('loginData', data))
        }}
    }
}
  • reducer/reduxs.js
    新增 ACTION_REGISTER, getReigster与导出 新的allReducer
import {SLIDECOLLAPSED, ROUTERCONFIG, ACTION_LOGIN, ACTION_REGISTER} from '@/reducer/action.js'
const getReigster = (state = {}, action) => {
    switch (action.type) {
        case ACTION_REGISTER:
            return {...state, ...action}
        default :
            return state
    }
}

export const allReducer = combineReducers({
    slidecollapsed: slidecollapsedFuc, routerConfig: getRouterConfig, getLogin: getLoginFun, getReigster
})

这样, 就修改完成了。

  1. 验证接口是否成功

之前在http.js修改了提示, 不管请求成功或是失败, 都会有弹框出来提示。
当然, 也可以查看内容是否注入到组件内
在register组件内, 在render内打印出 this.pros出来,
还记得我们再 connect.js内添加的内容么?

    mapDispatchToProps (dispatch) {
        return {hanleRegister: (data) => {
                return dispatch(fetchPosts('/register', ACTION_REGISTER,'reigsterData', data))
            }}
    }

这里的reigsterData就是注入到组件内的内容。 查看是否有该内容, 如果有, 查看是否是请求后的返回值。
里面包含这个参数, 表示注册成功。

{
    regsterData: {
        code : 200
        message : "注册成功"
    }
}
  1. 注册完毕后, 自动登录
  • 修改 reducer/connect.js

添加 handleLogin 登录事件

export const mapReigster = {
    mapStateToProps (state) {
        return state.getReigster || state
    },
    mapDispatchToProps (dispatch) {
        return {hanleRegister: (data) => {
                return dispatch(fetchPosts('/register', ACTION_REGISTER,'reigsterData', data))
            },
            handleLogin: (data) => {
                return dispatch(fetchPosts('/login', ACTION_REGISTER, 'loginData', data))
            }
        }
    }
}
  • views/register/index.js
    state = {
        confirmDirty: false,
        autoCompleteResult: [],
        formValue: [],  // 添加保存内容
        isLoginLoading: false // 添加是否已经请求
    };

        handleSubmit = (e) => {
            e.preventDefault();
            this.props.form.validateFieldsAndScroll((err, values) => {
                if (!err) {
                    console.log('Received values of form: ', values);
                    this.props.hanleRegister(values)
                    this.setState({  // 注册完毕, 保存内容,并修改状态。
                        formValue: values,
                        isLoginLoading: false
                    })
                }
            });
        }
        // render内
        let { reigsterData, loginData } = this.props  // 新增注入的数据
        // 注册成功, 自动登录
        if (typeof reigsterData === 'object' && reigsterData.code === 200) {
            if (!this.state.isLoginLoading) { // 判断是否已经请求过,
                this.props.handleLogin(this.state.formValue) // 请求登录。
                this.setState({
                    isLoginLoading: true
                })
            }
            if (typeof loginData === 'object' && loginData.code === 200) { // 登录成功, 跳转页面。
                sessionStorage.setItem('isAuthenticated', true)
                let from = {}
                from.pathname = '/';
                return ;
            }
        }

这样, 注册与注册完毕后自动登录就已经完成。

需要注意的是, 这里是自己写的一个后台, 登录账号与密码已经写死, admin 123456, 所以, 这里注册的时候, 虽然随便输入什么都能够注册成功, 但是登陆的时候, 只有admin 123456才能够登录成功。

你可能感兴趣的:(一步步搭建react后台系统4)