《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.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再刷新即会跳转到登录页面