React开发后台管理系统项目笔记

21,263 阅读12分钟

主页面架构设计

页面结构定义

  • 左侧导航栏,右侧显示内容
  • 右侧显示内容分别分为Header、中Content和下Footer部分

目录结构定义

  • 定义components包,分别加入Footer、Header、NavLeft包,内部加入index.js文件,导入时可以导入到包的级别,系统会自动寻找包下的index.js文件

  • 定义style包,内部加入common.less文件定义全局样式

栅格系统使用

  • 栅格系统一共24列
  • Antd中行用Row组件,列使用Col组件,列存在span属性(span={长度值}的方式写入span属性),同一Row下的Col span总和为24

calc计算方法使用

  • clac()是less中动态计算长度值
  • 任何长度值都可以用clac()函数进行计算
  • calc(100vh):vh的含义相当于1%,100vh即是100%

例子使用:

width:clac(100%-50px)   //表示宽度属性是整个布局的100%减去50px的长度

关于less

  • less是预编辑器
  • 在div下有a标签,想设定a的样式和div的样式,在less中可以在定义时嵌套
//css中
div{...}
div a{...}

//less中
div{
    ...
    a:{
      ...  
    }
}
  • less可以使用定义的变量
@colorA:'red'
div{
    color:@colorA
    a:{
        color:black  
    }
}

###左侧导航栏内容

在public文件夹下添加assets文件夹,放置logo-ant.svg图片

注意:public文件是build文件是build之后存储内容的文件,通常内部放置静态资源,public下的文件内容在访问时全是通过根目录直接访问文件地址

实例代码首页头部内容

利用jsonp可以解决跨域问题(所谓跨域就是跨域名,跨端口,跨协议)

利用setInterval函数实时刷新时间信息,并调用axios包中的jsonp方法发送请求并根据promise进行回调

Router 4.0

React Router4.0基本概念介绍

4.0版本已不需要路由配置,一切皆组件

  • react-router:基础路由包

    提供了一些router的核心api,包括Router,Route,Switch等

  • react-router-dom:基于浏览器端的路由(包含react-router)

    提供了BrowserRouter,HashRouter,Route,Link,NavLink

    安装:npm install react-router-dom --save 或 yarn add react-router-dom

  • react-router-dom核心用法

    HashRouter和BrowserRouter

    Route:path、exact、component、render

    注:exact属性代表精准匹配,必须path完全匹配才可以加载对应组件

  • NavLink应用于菜单里作为菜单导航、Link超链接导航

eg1.
import {Link} from 'react-router-dom';
const Header=()=>{
    <header>
        <nav>
            <li><Link to='/'>Home</Link></li>
            <li><Link to='/about'>About</Link></li>
            <li><Link to='/three'>Three</Link></li>
        </nav>
    </header>
}
  • 定义:取值:this.props.match.params.number
<Link to={{pathname:'/three/7'}}>Three #7</Link>
  • 同时在to属性中可以传递一个location对象,如:
{pathname:'/',search:'',hash:'',key:'abc123',state:{}}
  • Switch-选择符合要求的Route,自上至下找到第一个可以匹配的内容,则不继续加载其他
<Switch>
    <Route path='/admin/ui/buttons' component={Buttons}/>
    <Route path='/admin/ui/models' component={Models}/>
    <Route path='/admin/ui/loading' component={Loading}/>
</Switch>
  • Redirect
路由重定向:<Redirect to="/admin/home">

React Router4.0 Demo介绍

  • 4.0基本路由功能DEMO实现-混合组件化【将Route和Link放在同一页面】

    HashRouter将Link和Router进行包裹,其内部必须只能有一个子节点

    通过Link组件的to属性设置路由地址;通过Route组件的path属性匹配路由地址,从而渲染对应component中的组件【注意:Route添加exact属性可以做到路径的精准匹配,否则/about既可以匹配/也可以匹配/about等路由地址】

  • 实战代码

  • 4.0基本路由功能DEMO实现-配置化【将Route路由提取到一个单独的JS文件中】

    配置化实现路由功能

    创建Home.js内部写上ul->li->Link导航组件,并在想要显示对应路由内容的区域写上{this.props.children}即会加载调用Home.js组建时内部传递的信息

    嵌套路由

    如想实现在Main组件中的嵌套路由,需要在Main组件中添加{this.props.children}从而渲染对应的内部信息,还需要添加Link组件以进行跳转

    之后在router.js中对应调用该组件的Route组件中,删除component属性,添加render属性进行页面渲染,render属性内应是一个函数,返回Main组件(内部带有Route属性以进行路由渲染)

    注意点:

    调用Main组件的Route内部render函数如果添加()=>{}则需要在函数体内写return,因为{}表示函数体,内部的函数将被执行,返回组件需要写return;如果不添加大括号则直接写返回的组件即可,ES6箭头函数默认箭头后面的内容是return的

  • 获取动态路由的值

在main.js中设置跳转的路由链接

import React,{Component} from 'react';
import {Link} from 'react-router-dom';
class Main extends Component{
    render(){
        return(
            <div>
                this is Main.<br/>
                <Link to="/main/test-id">嵌套路由1</Link><br/>
                <Link to="/main/456">嵌套路由2</Link>
                <hr></hr>
                {this.props.children}
            </div>
        );
    }
}
export default Main;

在Info.js中获取定义的动态路由内容信息,通过{this.props.match.params.路由的名称}

import React,{Component} from 'react';
class Info extends Component{
    render(){
        return(
            <div>
                这里是测试动态路由功能
                动态路由的值是{this.props.match.params.value}
            </div>
        );
    }
}
export default Info;
  • 添加默认路由

    添加react-router-dom的Switch组件包裹Route组件用于设置自上自下只匹配一个路由

    添加没有path属性的Route组件放置Switch组件内部的最后位置,作为默认路由

NoMath.js

import React from 'react';
class NoMatch extends React.Component{
    render(){
        return(
            <div>
                404 Not Found
            </div>
        );
    }
}
export default NoMatch;

项目路由实战开发

由于用户访问项目时输入url需要有对应的输出,而作为整个文件输出时,一共有三种情况:登录、详情页、首页。故需要编写项目的入口文件router.js并在index.js中引入

router文件中定义使用路由的方式为HashRouter

由于我们要访问完整路由时有登录页面、详情页面和首页,router文件需要定义根组件App,App.js内部什么都没有只有{this.props.children},主要用来存放子组件(即上述三个页面)。【如果没有用App进行包裹,即没有地方设置{this.props.children}显示路由的页面内容,即上述三个页面没法显示】

总结:想利用Route显示组件信息,则必须调用{this.props.children}显示其页面的组件

将Admin组件中的content部分使用{this.props.children}显示在router.js中的Route得到的页面

/src/admin.js

import React from 'react';
import { Row, Col } from 'antd';
import Header from './components/Header';
import Footer from './components/Footer';
import NavLeft from './components/NavLeft';
import Home from './pages/home';
import './style/common.less'
class Admin extends React.Component{
  render(){
      return(
          <Row className="container">
              <Col span={6} className="nav-left">
                  <NavLeft/>
              </Col>
              <Col span={18} className="main">
                  <Header/>
                  <Row className="content">
                      {/* <Home></Home> */}
                      {this.props.children}
                  </Row>
                  <Footer/>
              </Col>
          </Row>
      );
  }
}
export default Admin;

有Route就一定要有Link指定路由地址,我们在首页中通过左侧导航栏进行跳转,故需要在NavLeft组件中利用react-router-dom的NavLink设置路由地址,NavLink组件显示的内容为{item.title},to跳转的地址为{item.key}

UI菜单各个组件使用(Andt UI组件)

按钮组件

  • 引入Card组件
import {Card} from 'antd'
  • title属性用于标注卡片上方标题
<Card title="基础组件"></Card>

cmd-markdown-logo

Button组件

  • 引入Button组件:
import {Button} from 'antd'
  • type属性值

    primary表示主按钮

    不写type表示默认样式按钮

    dashed表示虚线按钮

    danger表示危险按钮

  • disable属性值表示禁用按钮

  • icon属性值表示按钮图标样式

    plus表示加号

    danger表示危险按钮

    delete表示删除

    search表示搜索

    download表示下载

  • shape属性表示按钮形状

    circle表示圆形

  • loading属性为{true}表示加载中(此时按钮不能点击)

  • 按钮组为Button.Group组件,用于表示包含的Button组件是一个组

  • size属性表示组件大小

    small小按钮组件

    default默认大小按钮组件

    large大按钮组件

Radio组件

  • 引入Rodio组件:
import {Radio} from 'antd'
  • Radio组件外部需要用Radio.Group组件包裹,并通过外部组件对象可以获得内部Radio组件的value值(通过e.target.value)

补充知识点

当Route页面内部信息超过当前页面大小时,会出现滚动条,左侧导航栏会跟着一起滚动,导航栏下方为空包

解决方案

当common.less中的main的定义添加overflow:auto,表示当渲染页面高度超过当前屏幕时,自动滚动

弹框组件-Modal基本组件

  • 引入Modal:
import {Modal} from 'antd';
  • Model组件属性

    title属性作为标题显示

    visible属性参数为{true|false},为true则显示,为false则不显示

    onCancel属性值为一个函数,执行当点击模态框的×或cancel选项时执行的方法

  • Model内部填写的内容将作为模板框的正文内容

  • 知识点

    组件的onClick值为this.handleName(即函数名)时,表示一开始就会自动调用,无法传参;当需要传参时,需要将onClick中的内容变为箭头函数,返回代参的调用方法从而实现点击时执行函数并传参调用方法

    传递的参数如果想作为对象的键时,需要用[]进行包裹,如果没包裹直接放置键的位置则视为变量而报错

  • Model自定义页脚实现方式

    Model组件的visible属性{true}或{false}实现是否显示

    Model组件的okText属性设置OK选项的显示内容

    Model组件的cancelText属性设置Cancel选项显示内容

  • Model顶部20px弹框实现方式

    利用style属性值为{{top:20}}设定距顶部20px

  • Model水平居中实现方式

    利用Model组件的wrapClassName设定样式名称

取出用户名及密码的值

AntD 通过getFieldDecorator 属性,来读取用户名及密码,直接进行使用,且使用之前必须通过Form.create()创建一个对象/表单,之后我们才能使用getFieldDecorator

this.props.form.getFieldDecorator是AntD已经封装好的,固定语法,要记住

const { getFieldDecorator } = this.props.form;

getFieldDecorator用法如下,其中在rules内定义规则

<FormItem>
	{
	     getFieldDecorator('userName',{
	         initialValue:'',
	         rules:[
	             {
	                 required:true,
	                 message:'用户名不能为空'
	             },
	             {
	                 min:5,max:10,
	                 message:'长度不在范围内'
	             },
	             {
	                 pattern:new RegExp('^\\w+$','g'),
	                 message:'用户名必须为字母或数字',
	             }
	         ]
	     })(
	     <Input prefix={<Icon type="user"/>} placeholder="请输入用户名"/>
	     )
	 }
</FormItem>

必须要有下面这句话。通过Form.create()创建表单,将组件传递进去,这样才能识别 getFieldDecorator,否则会报错

//关键,一定要通过Form.create()创建一个对象/表单,将组件传递进去,这样才能识别 getFieldDecorator
export default Form.create()(FormLogin);

效验输入的信息

  • /getFieldValue是object对象,form的一个方法,用于获取表单下所有的值
  • validateFields方法是校验信息是否符合要求,校验下是一个循环,用()=>
handleSubmit = () =>{
        //getFieldsValue是object对象,form的一个方法,用于获取表单下所有的值
        let userInfo = this.props.form.getFieldsValue();
        //validateFields方法是校验信息是否符合要求,校验下是一个循环,用()=>
        this.props.form.validateFields((err,values)=>{
            if(!err){
                message.success(`${userInfo.userName}恭喜你,登陆成功!当前密码为:${userInfo.userPwd}`)
            }
        })
    }

CheckBox记住密码

一定要在getFieldDecorator里声明valuePropName:'checked',才能默认勾选Checkbox 的按钮

<FormItem>
	{
		getFieldDecorator('remember',{
			valuePropName:'checked',
			 	initialValue:true
			 })(
				 <Checkbox >记住密码</Checkbox>
			 )
		 }
	 <a href="#" style={{float:'right'}}>忘记密码</a>
</FormItem>

增加图标

使用prefix={}

<Input prefix={<Icon type="user"/>} placeholder="请输入用户名"/>
<Input prefix={<Icon type="lock"/>} placeholder="请输入密码"/>

整体效果如图:

cmd-markdown-logo

注册

联系地址

在设置联系地址的范围最大行数,最小行数时:autoSize={} 里面要求是一个对象。如const rows = {minRows:2,manRows:6}; autoSize={rows } ;成立。autoSize={minRows:2,manRows:6} 将报错。 AntD官方文档描述如下:

cmd-markdown-logo

    <FormItem label="联系地址" {...formItemLayout}>
        {
            getFieldDecorator('address',{
                initialValue:'北京市海淀区奥林匹克公园'
            })(
                <TextArea
                    autosize={rowObject}
                />
            )
        }
    </FormItem>

效果如图:

cmd-markdown-logo

日期显示

同时显示日期和时间 要showTime 和 format=‘YYYY–MM–DD HH:mm:ss’同时使用

    <FormItem label="生日" {...formItemLayout}>
        {
            getFieldDecorator('birthday',{
                initialValue:moment('2018-08-08')
            })(
                <DatePicker
                    showTime
                    format="YYYY-MM-DD HH:mm:ss"
                />
            )
        }
    </FormItem>

如图

cmd-markdown-logo

上传头像

  • 实际做项目中,与现在有所不同

    要用自己的接口替换 action="//jsonplaceholder.typicode.com/posts/"里的内容

    不需要 getBase64 方法,直从接口返回上传成功的URL地址,服务端把图片存到服务器里,返回前段一个服务器的图片地址,我们把图片地址放在img的src展示

antD上传头像使用.jpg的图片格式

 //获取文件格式的字符串
getBase64 = (img, callback)=>{
	const reader = new FileReader();
    reader.addEventListener('load', () => callback(reader.result));
    reader.readAsDataURL(img);
}
handleChange = (info) => {
    if (info.file.status === 'uploading') {
        this.setState({ loading: true });
        return;
    }
    if (info.file.status === 'done') {
        // Get this url from response in real world.
        this.getBase64(info.file.originFileObj, imageUrl => this.setState({
            userImg:imageUrl,
            loading: false,
        }));
    }
}

<FormItem label="头像" {...formItemLayout}>
    {
        getFieldDecorator('userImg',{
        })(
            <Upload
                listType="picture-card"
                showUploadList={false}
                action="//jsonplaceholder.typicode.com/posts/"
                onChange={this.handleChange}
            >
                {this.state.userImg?<img src={this.state.userImg} />:<Icon type="plus"/>}
            </Upload>
        )
    }
</FormItem>

表格-基础表格

发送请求前加载

  //发送请求前加载
    if(options.data && options.data.isShowLoading !== false){
        loading = document.getElementById('ajaxLoading');
        //block显示loading
        loading.style.display = 'block';
    }

请求成功后关闭loading

//请求成功后关闭loading
            if(options.data && options.data.isShowLoading !== false){
                loading = document.getElementById('ajaxLoading');
                //none 关闭loading
                loading.style.display = 'none';
            }

注意:不想有loading时,要设置ShowLoading:false,默认为true

axios.ajax({
            url:'/table/list',
            data:{
                params:{
                    page:this.params.page
                },
                //不想有loading时。默认为true
                // isShowLoading :false,
            }
        }).then((res)=>{
            
        })
    }

表格-高级表格

头部固定

cmd-markdown-logo
table标签里设置scroll={{scroll={{y:240}}

y:y轴、240:y轴长度

<Card title="头部固定">
    <Table
        bordered
        columns={columns }
        dataSource={this.state.dataSource}
        pagination={false}
        scroll={{y:240}}
    />
</Card>

两侧固定

table标签里设置scroll={{scroll={{x:1050}}

x:x轴、1050:x轴总宽度,width之和

<Card title="左侧固定" style={{margin: '10px 0'}}>
    <Table
        bordered
        columns={columns2 }
        dataSource={this.state.dataSource}
        pagination={false}
        scroll={{x:1050}}
    />
</Card>

在标题栏下设置fixed:'left';fixed:'right'

        const columns2 = [
            {
                title: 'id',
                dataIndex: 'id',
                width:80,
                fixed:'left'
            },
            {
                title: '用户名',
                dataIndex: 'userName',
                fixed:'left',
                width:80,
            },
            {
                title: '性别',
                dataIndex: 'sex',
                render(sex){
                    return sex == 1 ? '男' : '女'
                },
                width:80,
            },
            {
                title: '生日',
                width:120,
                dataIndex: 'birthday'
            },

            {
                title: '早起时间',
                dataIndex: 'time',
                width:80,
            },
            {
                title: '地址',
                width:120,
                fixed:'right',
                dataIndex: 'address',
            },
        ]

若中间有缝隙,可增加标题种类

cmd-markdown-logo
若中间有过度太窄,修改scroll={{x:1050}},修改width之和

安装插件

react-draft-wysiwyg:文本编辑器插件

draftjs-to-html:文本转换为html的插件

yarn add react-draft-wysiwyg draftjs-to-html --save

城市管理页面

开通城市-弹框功能

  • 为以上组件,增加点击按钮 弹窗(Modal)功能

[开通城市]按钮:监听 onClick 事件,调用this.handleOpenCity()显示弹框

state = {
    list:[],
   isShowOpenCity:false      // 默认不可见
}

// 开通城市
 handleOpenCity = ()=>{
    this.setState({
            isShowOpenCity:true
    })
 }