《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再刷新即會跳轉到登錄頁面


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM