《React后台管理系统实战 :一》:目录结构、引入antd、引入路由、写login页面、使用antd的form登录组件、form前台验证、高阶函数/组件

实战

上接,笔记:https://blog.csdn.net/u010132177/article/details/104150177
https://gitee.com/pasaulis/react-guli

1)创建目录

src 目录下
	api ajax相关
	assets 公用资源
	components 非路由组件
	config 配置
	pages 路由组件
	utils 工具模块
	Appj.s 应用根组件
	index.js 入口js

cmd指创建:

mkdir api assets components config pages utils 

2)配置路由、引入antd

https://blog.csdn.net/u010132177/article/details/103344017

3)重置css样式

在public下新建css目录,放入如下文件reset.css,并在index.html里引入

 <link rel="stylesheet" href="/css/reset.css">
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
  font-weight: normal;
}

ul {
  list-style: none;
}

button,
input,
select,
textarea {
  margin: 0;
}

html {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

img,
video {
  height: auto;
  max-width: 100%;
}

iframe {
  border: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}

td:not([align]),
th:not([align]) {
  text-align: left;
}

4)用用axios编写ajax请求组件目录[src/api/]

1.ajax.js主要为用axios写异步的get,post请求最基础部分

import axios from 'axios'

export default function ajax(url,data={},type='GET'){
    if(type==='GET'){
        return axios.get(url,{
            params:data
        })
    } else {
        return axios.post(url,data)
    }
}

2.index.js主要为调用ajax组件编写各个对应接口的请求函数的两种写法

import ajax from './ajax'

// const BASE = 'http://localhost:5000'
const BASE = ''

//【1】导出一个函数,第1种写法
//登录接口函数
// export function reqLogin(username,password){
//     return ajax('login',{username,password},'POST')
// }

//【2】导出一个函数,第2种写法
// 登录接口函数
export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')

//添加用户接口
export const AddUser=(user)=>ajax(BASE+'/manage/user/add',user,'POST')

3.开发环境配置代理接口,用于处理跨域请求package.json

  • 如果不添加将无法跨域,显示为404 not found
  • 运行环境将用另一种方法配置代理接口
  "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  //最下面添加此句即可
  "proxy":"http://localhost:5000"
}

配置修改后记录重启项目才有用,ctrl+c、npm start

5)写页面:page/login/login.jsx

1.编写页面基本样式

2.使用antd的form登录组件

3.编写登录组件的本地验证

4.用axios编写ajax请求

import React,{Component} from 'react'
import login from '../../assets/images/logo.png'
import './login.less'
import { Form, Icon, Input, Button, Checkbox } from 'antd';
import {reqLogin} from '../../api/' //因为api文件夹下有index.js所以只要指定到文件夹即可

class Login extends Component{
    constructor(props){
        super(props);
    }

	//点提交按钮后的操作
    handleSubmit = e => {
        e.preventDefault();
        this.props.form.validateFields((err, values) => {
          if (!err) {//如果本地验证不存在错误,即正确返回
            //console.log('在此处发起axios请求验证,发送用户名,密码给服务器,即:', values);
            const {username,password}=values //解构本地values给username,password,用于发送给服务器
            //调用src/api/index.js的ajax登录请求,发送数据
            reqLogin(username,password).then(response=>{//处理正常响应
                console.log(response.data)
            }).catch(err=>{//处理出错信息
            	console.log(err)
            })
          }else{
              console.log('验证失败')
          }
        });
      };

    // 密码校验
    validatePwd=(rule,value,callback)=>{
        console.log('validatePwd()', rule, value)
        if(!value){
            callback('密码必须输入!')
        }else if(value.length<4){
            callback('密码必须大于4位')
        }else if(value.length>12){
            callback('密码不能超过12位')
        }else if(!/^[a-zA-Z0-9_]+$/.test(value)){
            callback('密码必须由字母、数字、下划线组成')
        }else{
            callback() //本地验证成功
        }
    }

    render(){
        //const form = this.props.form
        //const { getFieldDecorator }=form
        const {getFieldDecorator}=this.props.form  //以上两句合二为一

        
        return(
           <div className='login'>

               <header className='login-header'>
                   <img src={login} />
                   <h1>深蓝后台管理系统</h1>
               </header>

               <section className='login-content'>
                    <h2>用户登录</h2>
                    <Form onSubmit={this.handleSubmit} className="login-form">
                        <Form.Item>
                            {
                                getFieldDecorator('username',{
                                    rules:[
                                        {required:true,whitespace:true,message:'用户名必须输入!'},
                                        {min:4,message:'用户名必须大于4位'},
                                        {max:12,message:'用户名最多只能12位'},
                                        {pattern:/^[a-zA-Z0-9_]+$/,message:'用户名只能是字母、数字、下划线'}
                                    ],
                                    //initialValue:'admin' //默认显示值
                                })(
                                <Input
                                    prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
                                    placeholder="用户名"
                                    />)
                            }
                            
                        </Form.Item>
                        <Form.Item>
                            {
                                getFieldDecorator('password',{
                                    rules:[
                                        { validator: this.validatePwd}
                                    ]
                                })(
                                <Input
                                    prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
                                    type="password"
                                    placeholder="密码"
                                    />)
                            }
                            
                        </Form.Item>
                        <Form.Item>
                            <Button type="primary" htmlType="submit" className="login-form-button">
                                登录
                            </Button>
                        </Form.Item>
                    </Form>
               </section>

           </div> 
        )
    }
}
const WrapLogin = Form.create()(Login)
export default WrapLogin

5.写样式src/login/login.less

.login{
    background: #fff url('./images/bg.jpg') ;
    background-size: 100% 100%;
    width:100%;
    height: 100%;

    .login-header{
        display: flex;
        align-items: center;
        height: 80px;
        background-color: rgba(21, 20, 13, 0.5);
        img{
            width: 40px;
            height: 40px;
            margin: 0 15px 0 50px;
        }
        h1{
            font-size: 30px;
            color: #fff;
            margin: 0px;
        }
    }

    .login-content{
        width: 400px;
        height: 300px;
        background-color: rgba(255, 255, 255, 0.7);
        margin: 50px auto;
        padding: 20px 40px;
        
        h2{
            text-align: center;
            font-size: 24px;
            margin-bottom: 20px;
        }

        .login-form-button{
            width: 100%;
        }
    }
}

6)简单登录

1.src/api/ajax.js

import axios from 'axios'
import {message} from 'antd'

export default function ajax(url, data={}, type='GET') {

  return new Promise((resolve, reject) => {
    let promise
    // 1. 执行异步ajax请求
    if(type==='GET') { // 发GET请求
      promise = axios.get(url, { // 配置对象
        params: data // 指定请求参数
      })
    } else { // 发POST请求
      promise = axios.post(url, data)
    }
    // 2. 如果成功了, 调用resolve(value)
    promise.then(response => {
      resolve(response.data)
    // 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
    }).catch(error => {
      // reject(error)
      message.error('请求出错了: ' + error.message)
    })
  })

}

2.src/api/index.js

import ajax from './ajax'

// const BASE = 'http://localhost:5000'
const BASE = ''

//【1】导出一个函数,第1种写法
//登录接口函数
// export function reqLogin(username,password){
//     return ajax('login',{username,password},'POST')
// }

//【2】导出一个函数,第2种写法
// 登录接口函数
export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')

//添加用户接口
export const AddUser=(user)=>ajax(BASE+'/manage/user/add',user,'POST')

3.src/pages/login/login.jsx

/*
能发送异步ajax请求的函数模块
封装axios库
函数的返回值是promise对象
1. 优化1: 统一处理请求异常?
    在外层包一个自己创建的promise对象
    在请求出错时, 不reject(error), 而是显示错误提示
2. 优化2: 异步得到不是reponse, 而是response.data
   在请求成功resolve时: resolve(response.data)
 */

import axios from 'axios'
import {message} from 'antd'

export default function ajax(url, data={}, type='GET') {
  return new Promise((resolve, reject) => {
    let promise
    // 1. 执行异步ajax请求
    if(type==='GET') { // 发GET请求
      promise = axios.get(url, { // 配置对象
        params: data // 指定请求参数
      })
    } else { // 发POST请求
      promise = axios.post(url, data)
    }
    // 2. 如果成功了, 调用resolve(value)
    promise.then(response => {
      resolve(response.data)
    // 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
    }).catch(error => {
      // reject(error)
      message.error('请求出错了: ' + error.message)
    })
  })


}

// 请求登陆接口
// ajax('/login', {username: 'Tom', passsword: '12345'}, 'POST').then()
// 添加用户
// ajax('/manage/user/add', {username: 'Tom', passsword: '12345', phone: '13712341234'}, 'POST').then()

4.其它src/app.js路由部分

import React,{Component} from 'react'
import {BrowserRouter,Route,Switch} from 'react-router-dom'
import Admin from './pages/admin/admin'
import Login from './pages/login/login'

class App extends Component{
    constructor(props){
        super(props);
    }

    render(){
        return(
           <BrowserRouter>
           <Switch>
               <Route path='/login' component={Login} />
               <Route path='/' component={Admin} />
           </Switch>
           </BrowserRouter> 
        )
    }
}
export default App

5.src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App/>,document.getElementById('root'))

7)登录功能完善 保存登录状态

localStrage的第三方库store:https://github.com/marcuswestin/store.js

库好处:

  1. 兼容所有浏览器
  2. 自动把数据解析为字典格式

1.src/utils/storageUtils.js

  1. 编写函数用于保存用户名到localstorage里去
  2. 从localSorage读取user
  3. 从localStorage删除user
/*
保存用户名到localStorage
*/
import store from 'store'
const USER_KEY='user_key' //定义localStorage内的键名为user_key

export default{
    //1.保存user到localStorage
    saveUser(user){
        //localStorage.setItem(USER_KEY,JSON.stringify(user)) //原生localStorage写法,下同
        store.set(USER_KEY,user)  //store库写法,自动把user解析为字典
    },

    //2.从localSorage读取user
    getUser () {
        // return JSON.parse(localStorage.getItem(USER_KEY)||'{}') //如果没有得到数据,就返回空字典
        return store.get(USER_KEY) || {}
    },

    //3.从localStorage删除user
    removeUser (){
        //localStorage.removeItem(USER_KEY)
        store.remove(USER_KEY)
    }
}

2. src/utils/memoryUtils.js

/*
用于在内存中保存数据的工具模块
*/
export default{
    user:{},
}

3. src/app.js

【1】引入模块
【2】读取local中保存user, 保存到内存中

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

import memoryUtils from './utils/memoryUtils' //引入【1】
import storageUtils from './utils/storageUtils'

// 【2】读取localstorage中保存的user, 保存到内存中,用于login.jsx页面读取是否登录
const user = storageUtils.getUser()
memoryUtils.user = user

ReactDOM.render(<App/>,document.getElementById('root'))

4.src/pages/login/login.jsx

【1】如果用户已经登陆, 自动跳转到管理界面

import React, {Component} from 'react'
import {Redirect} from 'react-router-dom'
import {
  Form,
  Icon,
  Input,
  Button,
  message
} from 'antd'
import './login.less'
import logo from '../../assets/images/logo.png'
import {reqLogin} from '../../api'
import memoryUtils from '../../utils/memoryUtils'
import storageUtils from '../../utils/storageUtils'


const Item = Form.Item // 不能写在import之前


/*
登陆的路由组件
 */
class Login extends Component {

  handleSubmit = (event) => {

    // 阻止事件的默认行为
    event.preventDefault()

    // 对所有表单字段进行检验
    this.props.form.validateFields(async (err, values) => {
      // 检验成功
      if (!err) {
        // console.log('提交登陆的ajax请求', values)
        // 请求登陆
        const {username, password} = values
        const result = await reqLogin(username, password) // {status: 0, data: user}  {status: 1, msg: 'xxx'}
        // console.log('请求成功', result)
        if (result.status===0) { // 登陆成功
          // 提示登陆成功
          message.success('登陆成功')

          // 保存user
          const user = result.data
          memoryUtils.user = user // 保存在内存中
          storageUtils.saveUser(user) // 保存到local中

          // 跳转到管理界面 (不需要再回退回到登陆)
          this.props.history.replace('/')

        } else { // 登陆失败
          // 提示错误信息
          message.error(result.msg)
        }

      } else {
        console.log('检验失败!')
      }
    });

    // 得到form对象
    // const form = this.props.form
    // // 获取表单项的输入数据
    // const values = form.getFieldsValue()
    // console.log('handleSubmit()', values)
  }

  /*
  对密码进行自定义验证
  */
  /*
   用户名/密码的的合法性要求
     1). 必须输入
     2). 必须大于等于4位
     3). 必须小于等于12位
     4). 必须是英文、数字或下划线组成
    */
  validatePwd = (rule, value, callback) => {
    console.log('validatePwd()', rule, value)
    if(!value) {
      callback('密码必须输入')
    } else if (value.length<4) {
      callback('密码长度不能小于4位')
    } else if (value.length>12) {
      callback('密码长度不能大于12位')
    } else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
      callback('密码必须是英文、数字或下划线组成')
    } else {
      callback() // 验证通过
    }
    // callback('xxxx') // 验证失败, 并指定提示的文本
  }

  render () {

    // 【1】如果用户已经登陆, 自动跳转到管理界面
    const user = memoryUtils.user
    if(user && user._id) {
      return <Redirect to='/'/>
    }

    // 得到具强大功能的form对象
    const form = this.props.form
    const { getFieldDecorator } = form;

    return (
      <div className="login">
        <header className="login-header">
          <img src={logo} alt="logo"/>
          <h1>React项目: 后台管理系统</h1>
        </header>
        <section className="login-content">
          <h2>用户登陆</h2>
          <Form onSubmit={this.handleSubmit} className="login-form">
            <Item>
              {
                /*
              用户名/密码的的合法性要求
                1). 必须输入
                2). 必须大于等于4位
                3). 必须小于等于12位
                4). 必须是英文、数字或下划线组成
               */
              }
              {
                getFieldDecorator('username', { // 配置对象: 属性名是特定的一些名称
                  // 声明式验证: 直接使用别人定义好的验证规则进行验证
                  rules: [
                    { required: true, whitespace: true, message: '用户名必须输入' },
                    { min: 4, message: '用户名至少4位' },
                    { max: 12, message: '用户名最多12位' },
                    { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是英文、数字或下划线组成' },
                  ],
                  initialValue: 'admin', // 初始值
                })(
                  <Input
                    prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
                    placeholder="用户名"
                  />
                )
              }
            </Item>
            <Form.Item>
              {
                getFieldDecorator('password', {
                  rules: [
                    {
                      validator: this.validatePwd
                    }
                  ]
                })(
                  <Input
                    prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
                    type="password"
                    placeholder="密码"
                  />
                )
              }

            </Form.Item>
            <Form.Item>
              <Button type="primary" htmlType="submit" className="login-form-button">
                登陆
              </Button>
            </Form.Item>
          </Form>
        </section>
      </div>
    )
  }
}

/*
1. 高阶函数
    1). 一类特别的函数
        a. 接受函数类型的参数
        b. 返回值是函数
    2). 常见
        a. 定时器: setTimeout()/setInterval()
        b. Promise: Promise(() => {}) then(value => {}, reason => {})
        c. 数组遍历相关的方法: forEach()/filter()/map()/reduce()/find()/findIndex()
        d. 函数对象的bind()
        e. Form.create()() / getFieldDecorator()()
    3). 高阶函数更新动态, 更加具有扩展性

2. 高阶组件
    1). 本质就是一个函数
    2). 接收一个组件(被包装组件), 返回一个新的组件(包装组件), 包装组件会向被包装组件传入特定属性
    3). 作用: 扩展组件的功能
    4). 高阶组件也是高阶函数: 接收一个组件函数, 返回是一个新的组件函数
 */
/*
包装Form组件生成一个新的组件: Form(Login)
新组件会向Form组件传递一个强大的对象属性: form
 */
const WrapLogin = Form.create()(Login)
export default WrapLogin
/*
1. 前台表单验证
2. 收集表单输入数据
 */

/*
async和await
1. 作用?
   简化promise对象的使用: 不用再使用then()来指定成功/失败的回调函数
   以同步编码(没有回调函数了)方式实现异步流程
2. 哪里写await?
    在返回promise的表达式左侧写await: 不想要promise, 想要promise异步执行的成功的value数据
3. 哪里写async?
    await所在函数(最近的)定义的左侧写async
 */

5.src/pages/admin/admin.jsx

import React,{Component} from 'react'
import {Redirect} from 'react-router-dom'
import memoryUtils from '../../utils/memoryUtils'


class Admin extends Component{
    constructor(props){
        super(props);
    }

    render(){
    //【1】如果memoryUtils中的user对象不存在(未登录),则跳转到登录页面
        const user=memoryUtils.user
        if(!user || !user._id){
            return <Redirect to='/login'/>
        }
        return(
           <div>
               Admin
           </div> 
        )
    }
}
export default Admin

6.效果http://localhost:3000

admin admin
输入错误则提示,成功则跳转到/admin页面
在这里插入图片描述
在这里插入图片描述
f12打开application,把localstorage里的user_key再刷新即会跳转到登录页面

posted @ 2020-02-15 15:10  晨光曦微  阅读(1640)  评论(0编辑  收藏  举报