實戰
上接,筆記: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.src/utils/storageUtils.js
- 編寫函數用於保存用戶名到localstorage里去
- 從localSorage讀取user
- 從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再刷新即會跳轉到登錄頁面
