实战
上接,笔记:https://blog.csdn.net/u010132177/article/details/104150177
https://gitee.com/pasaulis/react-guli
1)创建目录
1 2 3 4 5 6 7 8 9 10 11
| 1src 目录下
2 api ajax相关
3 assets 公用资源
4 components 非路由组件
5 config 配置
6 pages 路由组件
7 utils 工具模块
8 Appj.s 应用根组件
9 index.js 入口js
10
11 |
cmd指创建:
1 2 3
| 1mkdir api assets components config pages utils
2
3 |
2)配置路由、引入antd
https://blog.csdn.net/u010132177/article/details/103344017
3)重置css样式
在public下新建css目录,放入如下文件reset.css,并在index.html里引入
1 2 3
| 1 <link rel="stylesheet" href="/css/reset.css">
2
3 |
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
| 1/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
2html,
3body,
4p,
5ol,
6ul,
7li,
8dl,
9dt,
10dd,
11blockquote,
12figure,
13fieldset,
14legend,
15textarea,
16pre,
17iframe,
18hr,
19h1,
20h2,
21h3,
22h4,
23h5,
24h6 {
25 margin: 0;
26 padding: 0;
27}
28
29h1,
30h2,
31h3,
32h4,
33h5,
34h6 {
35 font-size: 100%;
36 font-weight: normal;
37}
38
39ul {
40 list-style: none;
41}
42
43button,
44input,
45select,
46textarea {
47 margin: 0;
48}
49
50html {
51 box-sizing: border-box;
52}
53
54*, *::before, *::after {
55 box-sizing: inherit;
56}
57
58img,
59video {
60 height: auto;
61 max-width: 100%;
62}
63
64iframe {
65 border: 0;
66}
67
68table {
69 border-collapse: collapse;
70 border-spacing: 0;
71}
72
73td,
74th {
75 padding: 0;
76}
77
78td:not([align]),
79th:not([align]) {
80 text-align: left;
81}
82
83 |
4)用用axios编写ajax请求组件目录[src/api/]
1.ajax.js主要为用axios写异步的get,post请求最基础部分
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1import axios from 'axios'
2
3export default function ajax(url,data={},type='GET'){
4 if(type==='GET'){
5 return axios.get(url,{
6 params:data
7 })
8 } else {
9 return axios.post(url,data)
10 }
11}
12
13 |
2.index.js主要为调用ajax组件编写各个对应接口的请求函数的两种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 1import ajax from './ajax'
2
3// const BASE = 'http://localhost:5000'
4const BASE = ''
5
6//【1】导出一个函数,第1种写法
7//登录接口函数
8// export function reqLogin(username,password){
9// return ajax('login',{username,password},'POST')
10// }
11
12//【2】导出一个函数,第2种写法
13// 登录接口函数
14export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')
15
16//添加用户接口
17export const AddUser=(user)=>ajax(BASE+'/manage/user/add',user,'POST')
18
19 |
3.开发环境配置代理接口,用于处理跨域请求package.json
1 2 3 4 5 6 7 8 9 10 11
| 1 "development": [
2 "last 1 chrome version",
3 "last 1 firefox version",
4 "last 1 safari version"
5 ]
6 },
7 //最下面添加此句即可
8 "proxy":"http://localhost:5000"
9}
10
11 |
配置修改后记录重启项目才有用,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 111 112 113
| 1import React,{Component} from 'react'
2import login from '../../assets/images/logo.png'
3import './login.less'
4import { Form, Icon, Input, Button, Checkbox } from 'antd';
5import {reqLogin} from '../../api/' //因为api文件夹下有index.js所以只要指定到文件夹即可
6
7class Login extends Component{
8 constructor(props){
9 super(props);
10 }
11
12 //点提交按钮后的操作
13 handleSubmit = e => {
14 e.preventDefault();
15 this.props.form.validateFields((err, values) => {
16 if (!err) {//如果本地验证不存在错误,即正确返回
17 //console.log('在此处发起axios请求验证,发送用户名,密码给服务器,即:', values);
18 const {username,password}=values //解构本地values给username,password,用于发送给服务器
19 //调用src/api/index.js的ajax登录请求,发送数据
20 reqLogin(username,password).then(response=>{//处理正常响应
21 console.log(response.data)
22 }).catch(err=>{//处理出错信息
23 console.log(err)
24 })
25 }else{
26 console.log('验证失败')
27 }
28 });
29 };
30
31 // 密码校验
32 validatePwd=(rule,value,callback)=>{
33 console.log('validatePwd()', rule, value)
34 if(!value){
35 callback('密码必须输入!')
36 }else if(value.length<4){
37 callback('密码必须大于4位')
38 }else if(value.length>12){
39 callback('密码不能超过12位')
40 }else if(!/^[a-zA-Z0-9_]+$/.test(value)){
41 callback('密码必须由字母、数字、下划线组成')
42 }else{
43 callback() //本地验证成功
44 }
45 }
46
47 render(){
48 //const form = this.props.form
49 //const { getFieldDecorator }=form
50 const {getFieldDecorator}=this.props.form //以上两句合二为一
51
52
53 return(
54 <div className='login'>
55
56 <header className='login-header'>
57 <img src={login} />
58 <h1>深蓝后台管理系统</h1>
59 </header>
60
61 <section className='login-content'>
62 <h2>用户登录</h2>
63 <Form onSubmit={this.handleSubmit} className="login-form">
64 <Form.Item>
65 {
66 getFieldDecorator('username',{
67 rules:[
68 {required:true,whitespace:true,message:'用户名必须输入!'},
69 {min:4,message:'用户名必须大于4位'},
70 {max:12,message:'用户名最多只能12位'},
71 {pattern:/^[a-zA-Z0-9_]+$/,message:'用户名只能是字母、数字、下划线'}
72 ],
73 //initialValue:'admin' //默认显示值
74 })(
75 <Input
76 prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
77 placeholder="用户名"
78 />)
79 }
80
81 </Form.Item>
82 <Form.Item>
83 {
84 getFieldDecorator('password',{
85 rules:[
86 { validator: this.validatePwd}
87 ]
88 })(
89 <Input
90 prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
91 type="password"
92 placeholder="密码"
93 />)
94 }
95
96 </Form.Item>
97 <Form.Item>
98 <Button type="primary" htmlType="submit" className="login-form-button">
99 登录
100 </Button>
101 </Form.Item>
102 </Form>
103 </section>
104
105 </div>
106 )
107 }
108}
109const WrapLogin = Form.create()(Login)
110export default WrapLogin
111
112
113 |
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 42 43
| 1.login{
2 background: #fff url('./images/bg.jpg') ;
3 background-size: 100% 100%;
4 width:100%;
5 height: 100%;
6
7 .login-header{
8 display: flex;
9 align-items: center;
10 height: 80px;
11 background-color: rgba(21, 20, 13, 0.5);
12 img{
13 width: 40px;
14 height: 40px;
15 margin: 0 15px 0 50px;
16 }
17 h1{
18 font-size: 30px;
19 color: #fff;
20 margin: 0px;
21 }
22 }
23
24 .login-content{
25 width: 400px;
26 height: 300px;
27 background-color: rgba(255, 255, 255, 0.7);
28 margin: 50px auto;
29 padding: 20px 40px;
30
31 h2{
32 text-align: center;
33 font-size: 24px;
34 margin-bottom: 20px;
35 }
36
37 .login-form-button{
38 width: 100%;
39 }
40 }
41}
42
43 |
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 27 28
| 1import axios from 'axios'
2import {message} from 'antd'
3
4export default function ajax(url, data={}, type='GET') {
5
6 return new Promise((resolve, reject) => {
7 let promise
8 // 1. 执行异步ajax请求
9 if(type==='GET') { // 发GET请求
10 promise = axios.get(url, { // 配置对象
11 params: data // 指定请求参数
12 })
13 } else { // 发POST请求
14 promise = axios.post(url, data)
15 }
16 // 2. 如果成功了, 调用resolve(value)
17 promise.then(response => {
18 resolve(response.data)
19 // 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
20 }).catch(error => {
21 // reject(error)
22 message.error('请求出错了: ' + error.message)
23 })
24 })
25
26}
27
28 |
2.src/api/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 1import ajax from './ajax'
2
3// const BASE = 'http://localhost:5000'
4const BASE = ''
5
6//【1】导出一个函数,第1种写法
7//登录接口函数
8// export function reqLogin(username,password){
9// return ajax('login',{username,password},'POST')
10// }
11
12//【2】导出一个函数,第2种写法
13// 登录接口函数
14export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')
15
16//添加用户接口
17export const AddUser=(user)=>ajax(BASE+'/manage/user/add',user,'POST')
18
19 |
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 43 44 45
| 1/*
2能发送异步ajax请求的函数模块
3封装axios库
4函数的返回值是promise对象
51. 优化1: 统一处理请求异常?
6 在外层包一个自己创建的promise对象
7 在请求出错时, 不reject(error), 而是显示错误提示
82. 优化2: 异步得到不是reponse, 而是response.data
9 在请求成功resolve时: resolve(response.data)
10 */
11
12import axios from 'axios'
13import {message} from 'antd'
14
15export default function ajax(url, data={}, type='GET') {
16 return new Promise((resolve, reject) => {
17 let promise
18 // 1. 执行异步ajax请求
19 if(type==='GET') { // 发GET请求
20 promise = axios.get(url, { // 配置对象
21 params: data // 指定请求参数
22 })
23 } else { // 发POST请求
24 promise = axios.post(url, data)
25 }
26 // 2. 如果成功了, 调用resolve(value)
27 promise.then(response => {
28 resolve(response.data)
29 // 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
30 }).catch(error => {
31 // reject(error)
32 message.error('请求出错了: ' + error.message)
33 })
34 })
35
36
37}
38
39// 请求登陆接口
40// ajax('/login', {username: 'Tom', passsword: '12345'}, 'POST').then()
41// 添加用户
42// ajax('/manage/user/add', {username: 'Tom', passsword: '12345', phone: '13712341234'}, 'POST').then()
43
44
45 |
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 23 24
| 1import React,{Component} from 'react'
2import {BrowserRouter,Route,Switch} from 'react-router-dom'
3import Admin from './pages/admin/admin'
4import Login from './pages/login/login'
5
6class App extends Component{
7 constructor(props){
8 super(props);
9 }
10
11 render(){
12 return(
13 <BrowserRouter>
14 <Switch>
15 <Route path='/login' component={Login} />
16 <Route path='/' component={Admin} />
17 </Switch>
18 </BrowserRouter>
19 )
20 }
21}
22export default App
23
24 |
5.src/index.js
1 2 3 4 5 6 7
| 1import React from 'react'
2import ReactDOM from 'react-dom'
3import App from './App'
4
5ReactDOM.render(<App/>,document.getElementById('root'))
6
7 |
7)登录功能完善 保存登录状态
库好处:
- 兼容所有浏览器
- 自动把数据解析为字典格式
1.src/utils/storageUtils.js
-
编写函数用于保存用户名到localstorage里去
-
从localSorage读取user
-
从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 26 27
| 1/*
2保存用户名到localStorage
3*/
4import store from 'store'
5const USER_KEY='user_key' //定义localStorage内的键名为user_key
6
7export default{
8 //1.保存user到localStorage
9 saveUser(user){
10 //localStorage.setItem(USER_KEY,JSON.stringify(user)) //原生localStorage写法,下同
11 store.set(USER_KEY,user) //store库写法,自动把user解析为字典
12 },
13
14 //2.从localSorage读取user
15 getUser () {
16 // return JSON.parse(localStorage.getItem(USER_KEY)||'{}') //如果没有得到数据,就返回空字典
17 return store.get(USER_KEY) || {}
18 },
19
20 //3.从localStorage删除user
21 removeUser (){
22 //localStorage.removeItem(USER_KEY)
23 store.remove(USER_KEY)
24 }
25}
26
27 |
2. src/utils/memoryUtils.js
1 2 3 4 5 6 7 8
| 1/*
2用于在内存中保存数据的工具模块
3*/
4export default{
5 user:{},
6}
7
8 |
3. src/app.js
【1】引入模块
【2】读取local中保存user, 保存到内存中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 1import React from 'react'
2import ReactDOM from 'react-dom'
3import App from './App'
4
5import memoryUtils from './utils/memoryUtils' //引入【1】
6import storageUtils from './utils/storageUtils'
7
8// 【2】读取localstorage中保存的user, 保存到内存中,用于login.jsx页面读取是否登录
9const user = storageUtils.getUser()
10memoryUtils.user = user
11
12ReactDOM.render(<App/>,document.getElementById('root'))
13
14 |
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 213 214
| 1import React, {Component} from 'react'
2import {Redirect} from 'react-router-dom'
3import {
4 Form,
5 Icon,
6 Input,
7 Button,
8 message
9} from 'antd'
10import './login.less'
11import logo from '../../assets/images/logo.png'
12import {reqLogin} from '../../api'
13import memoryUtils from '../../utils/memoryUtils'
14import storageUtils from '../../utils/storageUtils'
15
16
17const Item = Form.Item // 不能写在import之前
18
19
20/*
21登陆的路由组件
22 */
23class Login extends Component {
24
25 handleSubmit = (event) => {
26
27 // 阻止事件的默认行为
28 event.preventDefault()
29
30 // 对所有表单字段进行检验
31 this.props.form.validateFields(async (err, values) => {
32 // 检验成功
33 if (!err) {
34 // console.log('提交登陆的ajax请求', values)
35 // 请求登陆
36 const {username, password} = values
37 const result = await reqLogin(username, password) // {status: 0, data: user} {status: 1, msg: 'xxx'}
38 // console.log('请求成功', result)
39 if (result.status===0) { // 登陆成功
40 // 提示登陆成功
41 message.success('登陆成功')
42
43 // 保存user
44 const user = result.data
45 memoryUtils.user = user // 保存在内存中
46 storageUtils.saveUser(user) // 保存到local中
47
48 // 跳转到管理界面 (不需要再回退回到登陆)
49 this.props.history.replace('/')
50
51 } else { // 登陆失败
52 // 提示错误信息
53 message.error(result.msg)
54 }
55
56 } else {
57 console.log('检验失败!')
58 }
59 });
60
61 // 得到form对象
62 // const form = this.props.form
63 // // 获取表单项的输入数据
64 // const values = form.getFieldsValue()
65 // console.log('handleSubmit()', values)
66 }
67
68 /*
69 对密码进行自定义验证
70 */
71 /*
72 用户名/密码的的合法性要求
73 1). 必须输入
74 2). 必须大于等于4位
75 3). 必须小于等于12位
76 4). 必须是英文、数字或下划线组成
77 */
78 validatePwd = (rule, value, callback) => {
79 console.log('validatePwd()', rule, value)
80 if(!value) {
81 callback('密码必须输入')
82 } else if (value.length<4) {
83 callback('密码长度不能小于4位')
84 } else if (value.length>12) {
85 callback('密码长度不能大于12位')
86 } else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
87 callback('密码必须是英文、数字或下划线组成')
88 } else {
89 callback() // 验证通过
90 }
91 // callback('xxxx') // 验证失败, 并指定提示的文本
92 }
93
94 render () {
95
96 // 【1】如果用户已经登陆, 自动跳转到管理界面
97 const user = memoryUtils.user
98 if(user && user._id) {
99 return <Redirect to='/'/>
100 }
101
102 // 得到具强大功能的form对象
103 const form = this.props.form
104 const { getFieldDecorator } = form;
105
106 return (
107 <div className="login">
108 <header className="login-header">
109 <img src={logo} alt="logo"/>
110 <h1>React项目: 后台管理系统</h1>
111 </header>
112 <section className="login-content">
113 <h2>用户登陆</h2>
114 <Form onSubmit={this.handleSubmit} className="login-form">
115 <Item>
116 {
117 /*
118 用户名/密码的的合法性要求
119 1). 必须输入
120 2). 必须大于等于4位
121 3). 必须小于等于12位
122 4). 必须是英文、数字或下划线组成
123 */
124 }
125 {
126 getFieldDecorator('username', { // 配置对象: 属性名是特定的一些名称
127 // 声明式验证: 直接使用别人定义好的验证规则进行验证
128 rules: [
129 { required: true, whitespace: true, message: '用户名必须输入' },
130 { min: 4, message: '用户名至少4位' },
131 { max: 12, message: '用户名最多12位' },
132 { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是英文、数字或下划线组成' },
133 ],
134 initialValue: 'admin', // 初始值
135 })(
136 <Input
137 prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
138 placeholder="用户名"
139 />
140 )
141 }
142 </Item>
143 <Form.Item>
144 {
145 getFieldDecorator('password', {
146 rules: [
147 {
148 validator: this.validatePwd
149 }
150 ]
151 })(
152 <Input
153 prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
154 type="password"
155 placeholder="密码"
156 />
157 )
158 }
159
160 </Form.Item>
161 <Form.Item>
162 <Button type="primary" htmlType="submit" className="login-form-button">
163 登陆
164 </Button>
165 </Form.Item>
166 </Form>
167 </section>
168 </div>
169 )
170 }
171}
172
173/*
1741. 高阶函数
175 1). 一类特别的函数
176 a. 接受函数类型的参数
177 b. 返回值是函数
178 2). 常见
179 a. 定时器: setTimeout()/setInterval()
180 b. Promise: Promise(() => {}) then(value => {}, reason => {})
181 c. 数组遍历相关的方法: forEach()/filter()/map()/reduce()/find()/findIndex()
182 d. 函数对象的bind()
183 e. Form.create()() / getFieldDecorator()()
184 3). 高阶函数更新动态, 更加具有扩展性
185
1862. 高阶组件
187 1). 本质就是一个函数
188 2). 接收一个组件(被包装组件), 返回一个新的组件(包装组件), 包装组件会向被包装组件传入特定属性
189 3). 作用: 扩展组件的功能
190 4). 高阶组件也是高阶函数: 接收一个组件函数, 返回是一个新的组件函数
191 */
192/*
193包装Form组件生成一个新的组件: Form(Login)
194新组件会向Form组件传递一个强大的对象属性: form
195 */
196const WrapLogin = Form.create()(Login)
197export default WrapLogin
198/*
1991. 前台表单验证
2002. 收集表单输入数据
201 */
202
203/*
204async和await
2051. 作用?
206 简化promise对象的使用: 不用再使用then()来指定成功/失败的回调函数
207 以同步编码(没有回调函数了)方式实现异步流程
2082. 哪里写await?
209 在返回promise的表达式左侧写await: 不想要promise, 想要promise异步执行的成功的value数据
2103. 哪里写async?
211 await所在函数(最近的)定义的左侧写async
212 */
213
214 |
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 25 26 27
| 1import React,{Component} from 'react'
2import {Redirect} from 'react-router-dom'
3import memoryUtils from '../../utils/memoryUtils'
4
5
6class Admin extends Component{
7 constructor(props){
8 super(props);
9 }
10
11 render(){
12 //【1】如果memoryUtils中的user对象不存在(未登录),则跳转到登录页面
13 const user=memoryUtils.user
14 if(!user || !user._id){
15 return <Redirect to='/login'/>
16 }
17 return(
18 <div>
19 Admin
20 </div>
21 )
22 }
23}
24export default Admin
25
26
27 |
admin admin
输入错误则提示,成功则跳转到/admin页面
f12打开application,把localstorage里的user_key再刷新即会跳转到登录页面