0%

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

详情请点阅读全文

实战

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

1)创建目录

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

cmd指创建:

1
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里引入

1
<link rel="stylesheet" href="/css/reset.css">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*! 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请求最基础部分

1
2
3
4
5
6
7
8
9
10
11
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组件编写各个对应接口的请求函数的两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
  • 运行环境将用另一种方法配置代理接口
1
2
3
4
5
6
7
8
9
  "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请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
能发送异步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路由部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
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

  3. 编写函数用于保存用户名到localstorage里去
  4. 从localSorage读取user
  5. 从localStorage删除user
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /*
    保存用户名到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

1
2
3
4
5
6
/*
用于在内存中保存数据的工具模块
*/
export default{
user:{},
}

3. src/app.js

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

1
2
3
4
5
6
7
8
9
10
11
12
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】如果用户已经登陆, 自动跳转到管理界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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再刷新即会跳转到登录页面