zoukankan      html  css  js  c++  java
  • 《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- 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%;
        100%;
        height: 100%;
    
        .login-header{
            display: flex;
            align-items: center;
            height: 80px;
            background-color: rgba(21, 20, 13, 0.5);
            img{
                 40px;
                height: 40px;
                margin: 0 15px 0 50px;
            }
            h1{
                font-size: 30px;
                color: #fff;
                margin: 0px;
            }
        }
    
        .login-content{
             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{
                 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再刷新即会跳转到登录页面

  • 相关阅读:
    Redis 补充
    python 魔法方法补充(__setattr__,__getattr__,__getattribute__)
    Mongodb 补充
    Mysql补充
    HTML
    优秀工具
    优秀文章收藏
    MySQL
    爬虫
    Python
  • 原文地址:https://www.cnblogs.com/chenxi188/p/12312200.html
Copyright © 2011-2022 走看看