美文网首页
【教程】react实战

【教程】react实战

作者: giraffecode9668 | 来源:发表于2019-11-17 00:46 被阅读0次

    React实战

    一、前期准备

    1、React安装与创建项目

    // 使用npm安装create-react-app
    npm install -g create-react-app
    
    // 在指定路径下使用create-react-app工具创建项目,xxx:项目名称
    create-react-app xxx
    
    // 进入项目,启动项目,测试项目是否创建成功
    cd xxx
    npm start
    
    

    详情参见 【react教程】

    2、组件的打包

    以调用antd为例,在使用antd时如果只是单纯的import { Button } from 'antd';,该按钮组件因没有导入对应的css样式而没有显示预期的效果,所以需要将外部库先打包

    方法一:全部导入

    在根目录的index.js导入相关包

    // 该方式属于导入文件的全部样式,不推荐这么做
    import 'antd/dist/antd.css'
    

    方法二:按需导入

    按需打包

    参考文档:https://ant.design/docs/react/use-with-create-react-app-cn

    /**
     * 先安装antd库
     */
    npm install antd
    
    /**
     * 安装按需打包依赖模块react-app-rewired customize-cra babel-plugin-import
     */
    npm install react-app-rewired customize-cra babel-plugin-import
    
    /**
     * 定义加载配置的 js 模块: 根目录/config-overrides.js
     */
    const {override, fixBabelImports, addLessLoader} = require('customize-cra');
    
    module.exports = override(
      // 针对antd实现按需打包: 根据import来打包(使用babel-plugin-import)
      fixBabelImports('import', {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: 'css',  // 自动打包相关的样式,如果自定义样式用 style: true
      }),
    
      // 使用less-loader对源码中的less的变量进行重新指定
      // 该函数是自定义主题样式,将primary样式指定成绿色,按需添加修改
     // addLessLoader({
     //   javascriptEnabled: true,
     //   modifyVars: {'@primary-color': '#1DA57A'},
     // }),
    )
    
    /**
     * 修改根目录/package.json配置,引入config-overrides.js配置
     */
    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test",
        "eject": "react-scripts eject"
      },
    

    完成上诉操作,antd库样式正常显示

    3、路由选择

    使用react-router-dom进行路由选择

    /**
     * 安装react-router-dom
     */
    npm install react-router-dom
    
    /**
     * 在项目路径下创建src/pages/admin/admin.jsx
     */
    
    import React, { Component } from 'react'
    
    /**
     * 后台管理的路由组件
     */
    export default class Admin extends Component {
        render() {
            return (
                <div>
                    <h1>Admin</h1>
                </div>
            )
        }
    }
    
    /**
     * 在项目路径下创建src/pages/login/login.jsx
     */
    
    import React, { Component } from 'react'
    
    /**
     * 登录的路由组件
     */
    export default class Login extends Component {
        render() {
            return (
                <div>
                    <h1>Login</h1>
                </div>
            )
        }
    }
    
    /**
     * App.js中进行路由选择
     */
     
    import React, { Component } from 'react'
    import './App.css';
    import {BrowserRouter, Route, Switch} from 'react-router-dom'
    import Login from './pages/login/login'
    import Admin from './pages/admin/admin'
    
    /**
     * 应用的根组件
     */
    export default class App extends Component {
     
      render() {
        return (
          <BrowserRouter>
            <Switch>
              <Route path='/login' component={Login}></Route>
              <Route path='/' component={Admin}></Route>
            </Switch>
          </BrowserRouter>
        )
      }
    
    }
    

    上诉操作,完成路由选择

    说明:BrowserRouter是"/",可替换使用HashRouter是"#/"

    <Switch>标签只渲染第一个匹配到的<路由组件>或<重定向组件>

    如果没有<Switch>那么所有匹配的路由都将渲染

    二、使用antd编写登录页面

    详细的步骤不一一指出,以下列出调用的函数以及具体功能的实现

    0、导入使用less(失败)

    npm install less less-loader
    
    npm run eject
    

    暴露config文件夹,修改webpack.config.js

    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\.module\.less$/;
    
    {
                  test: lessRegex,
                  exclude: lessModuleRegex,
                  use: getStyleLoaders({
                    importLoaders: 2,
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                  }),
                  // Don't consider CSS imports dead code even if the
                  // containing package claims to have no side effects.
                  // Remove this when webpack adds a warning or an error for this.
                  // See https://github.com/webpack/webpack/issues/6571
                  sideEffects: true,
                },
                // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
                // using the extension .module.css
                {
                  test: lessModuleRegex,
                  use: getStyleLoaders({
                    importLoaders: 2,
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                    modules: true,
                    getLocalIdent: getCSSModuleLocalIdent,
                  }),
                },
    
    //可能会报错
    
    npm install react-scripts
    

    1、内置校验

    /**
     * 将Login组件包裹,向Login组件传递一个props中带有form,form带有各种方法
     */
    import { Form } from 'antd';
    
    class Login extends Component {
         
    }
    const WrapLogin = Form.create()(Login)
    export default WrapLogin
    
    /**
     * 在render中调用form对象内置getFieldDecorator方法进行校验
     */
    render() {
    
            // 得到具有强大功能的form对象
            const { getFieldDecorator } = this.props.form
    
            return( 
                
                // ...代码省略
                
                <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: '用户名必须是英文、数字或下划线组成' }
                    ],
                })(
                    <Input
                    prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
                    placeholder="用户名"
                    />,
                )}
                </Form.Item>
                </Form>
                 
                // ...代码省略
            )
        }
    

    高级组件:getFieldDecorator('id',{规则})(渲染标签)

    参考:https://ant.design/components/form-cn/#Form.create(options)

    2、自定义校验

    自定义校验同内置校验,需要引入form组件,步骤同 1、内置校验

    /**
     * getFieldDecorator的rules中使用validator规则
     * 指定自定义验证方法validatePwd
     */
    
    class Login extends Component {
         
        /**
         * 对密码进行自定义验证
         */
        validatePwd = (rule, value, callback) => {
            console.log('validatePwd', rule, value)
    
            // 验证失败
            // callback('xxxx')
    
            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(){
            // 得到具有强大功能的form对象
            const { getFieldDecorator } = this.props.form
    
            return( 
                <Form>
                <Form.Item>
                {getFieldDecorator('password', {
                    // 声明式验证:直接使用定义好的验证规则进行验证
                    rules: [
                        { required: true, whitespace: true, message: '用户名必须输入!' },
                        
                        // 不同点,这里引入自定义方法
                        { validator: this.validatePwd }
                        
                    ],
                })(
                    <Input
                    prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
                    type="password"
                    placeholder="密码"
                    />,
                )}
                </Form.Item>
                </Form>
            )
        }
    
    }
    
    

    3、提交表单

    /**
     * 表单提交方法
     */
    
    // Component 中的方法
     handleSubmit = (event) => {
    
            // 阻止事件的默认行为
            event.preventDefault()
    
            // form.validateFields 校验方法判断,err是状态,values是input值
            this.props.form.validateFields((err, values) => {
                //校验成功
                if (!err) {
                    console.log('提交登录的ajax请求', values)
                } else {
                    console.log('校验失败', values)
                }
            })
                
            //form.getFieldsValue() 获取一组输入控件的值,如不传入参数,则获取全部组件的值
    }
    
      
    //render中的Form标签
    <Form onSubmit={this.handleSubmit} > 
          // ...省略代码
    </Form>
    

    注意:使用 getFieldsValue getFieldValue setFieldsValue 等时,应确保对应的 field 已经用 getFieldDecorator 注册过了。

    方法 说明 类型
    getFieldsValue 获取一组输入控件的值,如不传入参数,则获取全部组件的值 Function([fieldNames: string[]])
    getFieldValue 获取一个输入控件的值 Function(fieldName: string)
    setFieldsValue 设置一组输入控件的值(注意:不要在 componentWillReceiveProps 内使用,否则会导致死循环,原因 ( { [fieldName]: value }, callback: Function ) => void

    4、axios

    // 安装axios模块
    npm install axios
    
     // 加上代理,实现跨域请求 package.json
     
     "proxy": "http://localhost:5000"
    
    /**
     * axios的一般用法
     * get方法调用axios.get,post方法调用axios.post
     * axios.get方法返回promise对象
     */
    axios.get(url, {
        // 请求参数
        params: data
    
    }).then(respinse => {
        // 成功的处理
        
    }).catch(error => {
        // 失败的处理
        
    })
    
    import axios from 'axios'
    import {message} from 'antd'
    
    /**
     * 对异步请求的axios库进行封装
     */
    export default function ajax(url, data = {}, method = 'GET') {
    
        // 优化
        return new Promise((resolve,reject) => {
            //定义变量
            let promise
    
            // 1. 执行异步ajax请求
            if (method === 'GET') {
                // 配置对象
                promise =  axios.get(url, {
                    //请求参数
                    params: data
                })
            } else  if (method === 'POST'){
                promise =  axios.post(url, data)
            }
    
            promise.then(response => {
                 // 2. 如果成功,调用resolve(value)
                 // 优化 resolve(value.data)
                resolve(response.data)
    
            }).catch(error => {
    
                // 3. 如果失败,不调用reject(reason),而是提示异常信息
                message.error('请求出错:'+error.message)
            })
        })
    
       
    }
    
    
    import ajax from './ajax'
    import jsonp from 'jsonp'
    import {message} from 'antd'
     
     /**
      * 对axios封装对象的调用,进行api封装,方便使用api请求
      */
    export const reqLogin = (username,password) => ajax('/login',{username,password},'POST')
    
    //  export function reqLogin(username,password) {
    //      return ajax('/login',{username,password},'POST')
    //  }
    
    /**
     * 调用api请求实现登录
     */
    handleSubmit = (event) => {
        // 阻止事件的默认行为
        event.preventDefault()
        
        // 校验
        this.props.form.validateFields(async (err, values) => {
            //校验成功
            if (!err) {
                
                // 传统promise方法
                reqLogin(username,password).then(response => {
                    console.log('成功了',response.data)
                }).catch(error => {
                    console.log('失败了',error)
                })
    
                // async await方法 有错误返回
                try {
                    const response = await reqLogin(username, password)
                    console.log('成功了', response.data)
                } catch (error) {
                    console.log('登录请求出错', error)
                    alert('请求出错:' + error.message)
                }
    
                // async await方法 无错误返回
                const result = await reqLogin(username, password)
                
                if (result.status === 0) {
                        // 登录成功
                        message.success('登录成功')
    
                        const user = result.data
                        // 保存在内存中
                        memoryUtils.user = user
    
                        // 保存到local中
                        storageUtils.saveUser(user)
    
                        // 跳转到后天管理界面(不需要回退到登录)
                        this.props.history.replace('/')
                    } else {
                        // 登录失败
                        message.error(result.msg)
                    }
            }
    
    

    asyncawait的搭配使用,可以优化代码的格式,作用同promise.then,异步处理回调结果

    5、本地缓存和内存管理

    /**
     * 使用localStorage可以将系统cookies保存到浏览器上
     * JSON.stringify()以json编码格式保存,如无,将以特定编码保存
     */
     
    // 保存 
    localStorage.setItem('user_key',JSON.stringify(user))
     
    // 读取
    JSON.parse(localStorage.getItem('user_key') || '{}')
    
    // 删除
    localStorage.removeItem('user_key')
    
    /**
     * localStorage可能存在浏览器不支持,引入store库,兼容所有浏览器
     * 编写封装的存储类:utils/storageUtils.js
     */
     
    // 安装store模块
    npm install sotre
    
    import store from 'store'
    
    const USER_KEY = 'user_key'
    
    export default {
    
        /**
         * 保存user
         */
        saveUser(user) {
            // localStorage.setItem(USER_KEY, JSON.stringify(user))
            
            store.set(USER_KEY,user)
        },
    
        /**
         * 读取user
         */
        getUser() {
            // return JSON.parse(localStorage.getItem(USER_KEY) || '{}')
    
            return store.get(USER_KEY) || {}
        },
    
        /**
         * 删除user
         */
        removerUser() {
            // localStorage.removeItem(USER_KEY)
    
            store.remove(USER_KEY)
        }
    }
    
    /**
     * 内存管理工具类
     * 用来在内存中保存数据的模块
     */
    
     export default {
        user: {},
     }
    
    /**
     * 方法调用
     */
    import memoryUtils from '../../utils/memoryUtils'
    import storageUtils from '../../utils/storageUtils'
    
    // 保存在内存中
    memoryUtils.user = user
    
    // 保存到local中
    storageUtils.saveUser(user)
    
    /**
     * 登录控制:
     * 本地缓存user,内存加入user
     */
      handleSubmit = (event) => {
    
            // 阻止事件的默认行为
            event.preventDefault()
    
            // 校验
            this.props.form.validateFields(async (err, values) => {
                //校验成功
                if (!err) {
    
                    // 请求登录
                    const { username, password } = values
    
                    // 传统promise方法
                    reqLogin(username,password).then(response => {
                        console.log('成功了',response.data)
                    }).catch(error => {
                        console.log('失败了',error)
                    })
    
    
                    // async await方法 有错误返回
                    try {
                        const response = await reqLogin(username, password)
                        console.log('成功了', response.data)
                    } catch (error) {
                        console.log('登录请求出错', error)
                        alert('请求出错:' + error.message)
                    }
    
                    // async await方法 无错误返回
                    const result = await reqLogin(username, password)
                    // console.log('成功了', response.data)
    
                    // // 返回数据
                    // const result = response.data
    
                    if (result.status === 0) {
                        // 登录成功
                        message.success('登录成功')
    
                        const user = result.data
                        // 保存在内存中
                        memoryUtils.user = user
    
                        // 保存到local中
                        storageUtils.saveUser(user)
    
                        // 跳转到后天管理界面(不需要回退到登录)
                        this.props.history.replace('/')
                    } else {
                        // 登录失败
                        message.error(result.msg)
                    }
    
                } else {
                    console.log('校验失败', values)
                }
            })
        }
    

    三、使用antd编写主页页面

    1、后台管理的路由组件--Admin

    /**
     * pages/admin/admin页面
     * 查看本地缓存是否有user选择显示页面
     */
    import React, { Component } from 'react'
    import { Redirect, Route, Switch } from 'react-router-dom'
    import memoryUtils from '../../utils/memoryUtils'
    import { Layout } from 'antd';
    
    const { Footer, Sider, Content } = Layout;
    
    export default class Admin extends Component {
        render() {
            const user = memoryUtils.user
    
            // 如果内存中没有存储user ==> 当前没有登录
            if (!user || !user._id) {
                // 自动跳转到登录
                return <Redirect to='/login' />
            }
    
            return (
                <Layout style={{ height: '100%' }}>
                    <Sider>
                        <LeftNav />
                    </Sider>
                    <Layout>
                        <Header>Header</Header>
                        <Content style={{ margin: 20, backgroundColor: '#fff' }}>
                            <Switch>
                                <Route path='/home' component={Home} />
                                <Route path='/category' component={Category} />
                                <Route path='/product' component={Product} />
                                <Route path='/role' component={Role} />
                                <Route path='/user' component={User} />
                                <Route path='/charts/bar' component={Bar} />
                                <Route path='/charts/line' component={Line} />
                                <Route path='/charts/pie' component={Pie} />
                                <Redirect to='/home' />
                            </Switch>
                        </Content>
                        <Footer style={{ textAlign: 'center', color: '#cccccc' }}>推荐使用谷歌浏览器,可以获得最佳页面操作体验</Footer>
                    </Layout>
                </Layout>
            )
        }
    }
    

    2、侧边菜单栏组件--LeftNav

    /**
     * 渲染函数
     */
    render() {
    
        const path = this.props.location.pathname
        const openKey = this.openKey
    
        return (
            <div className="left-nav">
                <Link to='/' className="left-nav-header">
                    <img src={logo} alt="logo" />
                    <h1>硅谷后台</h1>
                </Link>
    
                <Menu
                    mode="inline"
                    theme="dark"
                    selectedKeys={[path]}
                    defaultOpenKeys={[openKey]}
                    >
    
    
                    //菜单相关.....代码
    
                </Menu>
    
            </div>
    
        )
    }
    
    /**
     * 渲染函数中直接写菜单名称的方法较直接,普通
     * 采用动态封装如下
     * config/menuConfig.js
     */
     const menuList = [
        {
            title: '首页', // 菜单标题名称
            key: '/home', // 对应的 path
            icon: 'home', // 图标名称
        },
        {
            title: '商品',
            key: '/products',
            icon: 'appstore',
            children: [ // 子菜单列表
                {
                    title: '品类管理',
                    key: '/category',
                    icon: 'bars'
                },
                {
                    title: '商品管理',
                    key: '/product',
                    icon: 'tool'
                },
            ]
        },
        {
            title: '用户管理',
            key: '/user',
            icon: 'user'
        },
        {
            title: '角色管理',
            key: '/role',
            icon: 'safety',
        },
        {
    
            title: '图形图表',
            key: '/charts',
            icon: 'area-chart',
            children: [
                {
                    title: '柱形图',
                    key: '/charts/bar',
                    icon: 'bar-chart'
                },
                {
                    title: '折线图',
                    key: '/charts/line',
                    icon: 'line-chart'
                },
                {
                    title: '饼图',
                    key: '/charts/pie',
                    icon: 'pie-chart'
                },
            ]
        },
    ]
    export default menuList
    
    /**
     * 完整的left-nav
     */
    import React, { Component } from 'react'
    import './index.less'
    import logo from '../../assets/images/logo.png'
    import { Link, withRouter } from 'react-router-dom'
    import menuConfig from '../../config/menuConfig'
    import { Menu, Icon, } from 'antd';
    const { SubMenu } = Menu;
    
    class LeftNav extends Component {
    
        /**
         * 根据menu的数据数组生成对应的项
         * 使用map+递归调用
         */
        getMenuNodes = (menuConfig) => {
    
            const path = this.props.location.pathname
    
            return menuConfig.map(item => {
                if (!item.children) {
                    return (
                        <Menu.Item key={item.key}>
                            <Link to={item.key}>
                                <span>
                                    <Icon type={item.icon} />
                                    <span>{item.title}</span>
                                </span>
                            </Link>
                        </Menu.Item>
                    )
                } else {
    
                    const cItem = item.children.find(cItem => cItem.key === path)
                    if (cItem) {
                        this.openKey = item.key
                    }
    
                    return (
                        <SubMenu
                            key={item.key}
                            title={
                                <span>
                                    <Icon type={item.icon} />
                                    <span>{item.title}</span>
                                </span>
                            }
                        >
                            {
                                this.getMenuNodes(item.children)
                            }
                        </SubMenu>
                    )
                }
            })
        }
    
        /**
         * 使用reduce() + 递归
         */
        getMenuNodes2 = (menuConfig) => {
    
            const path = this.props.location.pathname
    
    
            return menuConfig.reduce((pre, item) => {
                if (!item.children) {
                    pre.push((
                        <Menu.Item key={item.key}>
                            <Link to={item.key}>
                                <span>
                                    <Icon type={item.icon} />
                                    <span>{item.title}</span>
                                </span>
                            </Link>
                        </Menu.Item>
                    ))
                } else {
                    pre.push((
                        <SubMenu
                            key={item.key}
                            title={
                                <span>
                                    <Icon type={item.icon} />
                                    <span>{item.title}</span>
                                </span>
                            }
                        >
                            {
                                this.getMenuNodes2(item.children)
                            }
                        </SubMenu>
                    ))
    
                    // 如果当前请求路由与当前菜单的某个子菜单的 key 匹配, 将菜单的 key 保存为 openKey
                    if (item.children.find(cItem => path.indexOf(cItem.key) === 0)) {
                        this.openKey = item.key
                    }
                }
                return pre
            }, [])
        }
    
        /**
         * 渲染之前执行一次
         */
        UNSAFE_componentWillMount() {
            this.menuNodes = this.getMenuNodes(menuConfig)
        }
    
        render() {
    
            const path = this.props.location.pathname
            const openKey = this.openKey
    
            return (
                <div className="left-nav">
                    <Link to='/' className="left-nav-header">
                        <img src={logo} alt="logo" />
                        <h1>硅谷后台</h1>
                    </Link>
    
                    <Menu
                        mode="inline"
                        theme="dark"
                        selectedKeys={[path]}
                        defaultOpenKeys={[openKey]}
                    >
                        {
                            this.menuNodes
                        }
                    </Menu>
    
                </div>
    
            )
        }
    }
    
    export default withRouter(LeftNav)
    

    3、正文头部组件--Header

    /**
     * 编写头部组件
     * 包括动态显示当前时间、天气、标题、用户名、退出
     */
    import React, { Component } from 'react'
    import { withRouter } from "react-router-dom";
    
    import { formateDate } from '../../utils/dateUtils'
    import memoryUtils from '../../utils/memoryUtils'
    import storageUtils from '../../utils/storageUtils'
    
    import menuConfig from '../../config/menuConfig'
    import { reqWeather } from '../../api/index';
    import './index.less'
    import { Modal } from 'antd';
    import LinkButton from '../link-button';
    
    
    /**
     * 顶部导航的组件
     */
    class Header extends Component {
    
        state = {
            // 当前时间字符串
            currentTime: formateDate(Date.now()),
    
            // 天气图片
            dayPictureUrl: '',
            // 天气
            weather: '',
        }
    
        getTime = () => {
            // 每隔1s获取当前时间,并更新状态数据currentTime
            this.intervalId = setInterval(() => {
                const currentTime = formateDate(Date.now())
                this.setState({ currentTime })
            }, 1000)
        }
    
        getWeather = async () => {
            // 调用接口请求异步获取数据
            const { dayPictureUrl, weather } = await reqWeather('北京')
    
            //更新状态
            this.setState({ dayPictureUrl, weather })
        }
    
        getTitle = () => {
            const path = this.props.location.pathname
            let title
            menuConfig.forEach(item => {
                if (item.key === path) {
                    // 如果当前的item对象的Key与path一样
                    title = item.title
                } else if (item.children) {
                    // 在所有的子item中查找匹配
                    const cItem = item.children.find(cItem => cItem.key === path)
                    // 如果有值才说明有匹配
                    if (cItem) {
                        title = cItem.title
                    }
                }
            })
            return title
        }
    
        /**
         * 退出登录
         */
        logout = () => {
            // 显示对话框
            Modal.confirm({
                content: '确定退出吗?',
                onOk: () => {
                    // 删除保存的user数据
                    storageUtils.removerUser()
                    memoryUtils.user = {}
    
                    // 跳转到login
                    this.props.history.replace('/login')
                }
            });
        }
    
        /**
         * 在第一次render()之后执行一次
         * 一般执行异步操作:发ajax请求/启动定时器
         */
        componentDidMount() {
            // 获取当前的时间
            this.getTime()
    
            // 获取当前天气
            this.getWeather()
        }
    
        /**
         * 在组件卸载之前调用
         */
        componentWillUnmount() {
            // 清除定时器
            clearInterval(this.intervalId)
        }
    
        render() {
            const { currentTime, dayPictureUrl, weather } = this.state
    
            const username = memoryUtils.user.username
    
            const title = this.getTitle()
    
            return (
                <div className='header'>
                    <div className='header-top'>
                        <span>欢迎,{username}</span>
                        <LinkButton  onClick={this.logout}>退出</LinkButton>
                    </div>
                    <div className='header-bottom'>
                        <div className='header-bottom-left'>{title}</div>
                        <div className='header-bottom-right'>
                            <span>{currentTime}</span>
                            <img src={dayPictureUrl} alt="weather" />
                            <span>{weather}</span>
                        </div>
                    </div>
    
                </div>
            )
        }
    }
    
    export default withRouter(Header)
    
    /*格式化日期工具 */
    export function formateDate(time) {
        if (!time) return ''
        let date = new Date(time)
        return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds()
    }
    

    四、品类管理的增加和修改

    1、使用card与table组件构建基本页面

    /**
     * Card 组件 
     * title在左侧,extra在右侧
     * Table 组件
     * dataSource 字段
     * columns 行
     * pagination={{ defaultPageSize: 5, showQuickJumper: true }} 设置分页器
     * loading 是否显示加载
     */
    
    <Card title={title} extra={extra} >
              <Table 
                  dataSource={parentId === '0' ? categorys : subCategorys}
                  columns={this.columns} 
                  bordered rowKey='_id'
                  pagination={{ defaultPageSize: 5, showQuickJumper: true }}
                  loading={loading} />
    </Card>
    
    /**
     * Table 数据格式
     */
    const dataSource = [
      {
        key: '1',
        name: '胡彦斌',
        age: 32,
        address: '西湖区湖底公园1号',
      },
      {
        key: '2',
        name: '胡彦祖',
        age: 42,
        address: '西湖区湖底公园1号',
      },
    ];
    
    const columns = [
      {
        title: '姓名',
        dataIndex: 'name',
        key: 'name',
      },
      {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
      },
      {
        title: '住址',
        dataIndex: 'address',
        key: 'address',
      },
    ];
    

    2、居中布局

    /**
     * css布局
     * display 生成容器
     * align-items 垂直
     * justify-content 
     */
    display: flex;
    align-items: center;
    justify-content: center;
    

    参考:https://www.cnblogs.com/qingchunshiguang/p/8011103.html

    3、模态框

    /**
     * 模态框可以使用状态showStatus设置
     */
    <Modal
        title="添加分类"
        visible={showStatus === 1}
        onOk={this.addCategory}
        onCancel={this.handleCancel}
        >
        <AddForm categorys={categorys} parentId={parentId} setForm={(form) => { this.form = form }} />
    </Modal>
    <Modal
        title="更新分类"
        visible={showStatus === 2}
        onOk={this.updateCategory}
        onCancel={this.handleCancel}
        >
        <UpdateForm categoryName={category.name} setForm={(form) => { this.form = form }} />
    </Modal>
    

    4、props传递父组件属性(属性方法)

    /**
     * 父组件,向子组件传递父组件的属性
     * categorys,parentId属性值
     * setForm 属性方法
     */
    <AddForm 
        categorys={categorys} 
        parentId={parentId} 
        setForm={(form) => { this.form = form }} />
    
    
    /**
     * 子组件,获取父组件的属性
     * categorys,parentId属性值
     * setForm 属性方法
     * 
     * 通过传递属性方法可以在子组件调用父组件的方法(逆向传递值)
     * 也可以使用ref方法(逆向传递值)
     */
     static propTypes = {
            categorys: PropTypes.array.isRequired,
            parentId: PropTypes.string.isRequired,
            setForm: PropTypes.func.isRequired
        }
    
     componentWillMount(){
            this.props.setForm(this.props.form)
        }
    
    
    

    5、input框可能会存在缓存

        // 清除输入数据
        this.form.resetFields()
    

    五、商品管理

    1、路由完全匹配

    exact : 路由完全匹配

    <Route path='/product' component={ProductHome} exact/>
    

    2、事件改变监听

            pagination={{
                defaultPageSize: PAGE_SIZE,
                showQuickJumper: true,
                total,
                onChange: (pageNum) => this.getProducts(pageNum)
            }}
    
             <Input
                 placeholder='关键字'
                 style={{ width: 150, margin: '0 15px' }}
                 value={searchName}
                 onChange={event => this.setState({ searchName: event.target.value })}
             />
                        
    

    3、Promise.all发送多个请求

        async componentDidMount() {
            const { pCategoryId, categoryId } = this.props.location.state.product
            if (pCategoryId === '0') {
                // 只有一级分类
                const result = await reqCategory(categoryId)
                const cName1 = result.data.name
                this.setState({ cName1: cName1 })
            } else {
                /*
                    //通过await请求,依次等待
                    // 二级分类
                    const result1 = await reqCategory(pCategoryId)
                    const result2 = await reqCategory(categoryId)
                    const cName1 = result1.data.name
                    const cName2 = result2.data.name
                */
    
                //一次性发送多个请求
                const results = await Promise.all([reqCategory(pCategoryId), reqCategory(categoryId)])
                const cName1 = results[0].data.name
                const cName2 = results[1].data.name
                
                this.setState({ cName1: cName1, cName2: cName2 })
            }
        }
    

    4、内置html

    <span dangerouslySetInnerHTML={{ __html: detail }}></span>
    

    5、api key-value对应写法

    /**
     * 搜索商品分页列表(根据商品名称/描述)
     */
    export const reqSearchProducts = ({ pageNum, pageSize, searchName, searchType }) => ajax('/manage/product/search', { pageNum, pageSize, [searchType]: searchName })
    

    6、Cascader级联选择

    /**
     * 组件
     */
           {
            getFieldDecorator('categoryIds', {
                initialValue: categoryIds,
                rules: [
                    { required: true, message: '必须输入商品分类' },
                ]
            })(
                <Cascader
                    placeholder='请指定商品分类'
                    options={this.state.options}
                    loadData={this.loadData}
                />
            )
        }
    
    // options是选项中的值
    const options = [
      {
        value: 'zhejiang',
        label: 'Zhejiang',
        isLeaf: false,
      },
      {
        value: 'jiangsu',
        label: 'Jiangsu',
        isLeaf: false,
      },
    ];
    
    // loadData是动态选择加载回调: 分类选择
        loadData = async selectedOptions => {
            const targetOption = selectedOptions[0];
            targetOption.loading = true;
    
            // 显示
            const subCategorys = await reqCategorys(targetOption.value)
    
            targetOption.loading = false;
    
            if (subCategorys.data && subCategorys.data.length > 0) {
                // 生成二级列表的Option
                const childOptions = subCategorys.data.map(c => ({
                    value: c._id,
                    label: c.name,
                    isLeaf: true
                }))
    
    
                // 关联当前的option上
                targetOption.children = childOptions
            } else {// 没有
                targetOption.isLeaf = true
            }
    
            this.setState({
                options: [...this.state.options],
            });
        };
    

    7、上传图片

    import React, { Component } from 'react'
    
    import { Upload, Icon, Modal, message } from 'antd';
    import { reqDeleteImg } from '../../api'
    import PropTypes from 'prop-types';
    import { BASE_IMG_URL } from '../../utils/constants';
    
    function getBase64(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => resolve(reader.result);
            reader.onerror = error => reject(error);
        });
    }
    
    /**
     * 用于图片上传的组件
     */
    export default class PicturesWall extends React.Component {
        static propTypes = {
            imgs: PropTypes.array
        }
    
        constructor(props) {
            super(props)
    
            let fileList = []
            const { imgs } = this.props
            if (imgs && imgs.length > 0) {
                fileList = imgs.map((img, index) => ({
                    uid: -index,
                    name: img,
                    status: 'done',
                    url: BASE_IMG_URL + img
                }))
            }
    
            this.state = {
                previewVisible: false,  //标识大图预览
                previewImage: '',
                fileList
            }
        }
    
    
    
        getImgs = () => this.state.fileList.map(file => file.name)
    
        handleCancel = () => this.setState({ previewVisible: false });
    
        handlePreview = async file => {
            if (!file.url && !file.preview) {
                file.preview = await getBase64(file.originFileObj);
            }
    
            this.setState({
                previewImage: file.url || file.preview,
                previewVisible: true,
            });
        };
    
        handleChange = async ({ file, fileList }) => {
    
            if (file.status === 'done') {
                const result = file.response
                if (result.status === 0) {
                    message.success('上传图片成功')
                    const { name, url } = result.data
                    file = fileList[fileList.length - 1]
                    file.name = name
                    file.url = url
                    console.log('done()', file)
    
                } else {
                    message.error('上传图片失败')
                }
            }
            else if (file.status === 'removed') {
                // 删除图片
                const result = await reqDeleteImg(file.name)
                if (result.status === 0) {
                    message.success('删除图片成功')
                } else {
                    message.error('删除图片失败')
                }
            }
    
    
            this.setState({
                fileList
            })
        };
    
        render() {
            const { previewVisible, previewImage, fileList } = this.state;
            const uploadButton = (
                <div>
                    <Icon type="plus" />
                    <div >Upload</div>
                </div>
            );
            return (
                <div>
                    <Upload
                        action="/manage/img/upload"   /** 上传图片接口 */
                        accept="image/*"  /** 只接收图片格式 */
                        listType="picture-card"
                        name='image'  /**请求参数名 */
                        fileList={fileList}
                        onPreview={this.handlePreview}
                        onChange={this.handleChange}
                    >
                        {fileList.length >= 3 ? null : uploadButton}
                    </Upload>
                    <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
                        <img alt="example" style={{ width: '100%' }} src={previewImage} />
                    </Modal>
                </div>
            );
        }
    }
    

    8、富文本编辑器

    // 下载相关依赖 
    npm install react-draft-wysiwyg draftjs-to-html
    

    react-draft-wysiwyg:编辑器

    draftjs-to-html:html语言转化为富文本语言

    /**
     * rich-text-editor组件
     */
    import React, { Component } from 'react';
    import { EditorState, convertToRaw, ContentState } from 'draft-js';
    import { Editor } from 'react-draft-wysiwyg';
    import draftToHtml from 'draftjs-to-html';
    import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
    import htmlToDraft from 'html-to-draftjs';
    import PropTypes from 'prop-types';
    
    export default class RichTextEditor extends Component {
    
        static propTypes = {
            detail: PropTypes.string
        }
    
    
        constructor(props) {
            super(props);
            const html = this.props.detail;
            if (html) {
                const contentBlock = htmlToDraft(html);
                if (contentBlock) {
                    const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
                    const editorState = EditorState.createWithContent(contentState);
                    this.state = {
                        editorState,
                    };
                }
            } else {
                this.state = {
                    editorState: EditorState.createEmpty(),
                }
            }
    
        }
    
        onEditorStateChange = (editorState) => {
            this.setState({
                editorState,
            });
        };
    
        getDetail = () => {
            // 返回对应数据的html格式文本
            return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))
        }
    
        // 上传图片回调函数
        uploadImageCallBack = (file) => {
            return new Promise(
                (resolve, reject) => {
                    const xhr = new XMLHttpRequest();
                    xhr.open('POST', '/manage/img/upload');
                    xhr.setRequestHeader('Authorization', 'Client-ID XXXXX');
                    const data = new FormData();
                    data.append('image', file);
                    xhr.send(data);
                    xhr.addEventListener('load', () => {
                        const response = JSON.parse(xhr.responseText);
                        resolve({ data: { link: response.data.url } });
                    });
                    xhr.addEventListener('error', () => {
                        const error = JSON.parse(xhr.responseText);
                        reject(error);
                    });
                }
            );
        }
    
        render() {
            const { editorState } = this.state;
            return (
                <div>
                    <Editor
                        editorState={editorState}
                        wrapperClassName="demo-wrapper"
                        editorClassName="demo-editor"
                        
                        // 编辑样式
                        editorStyle={{ border: '1px solid ', minHeight: 200, paddingLeft: 10 }}
                        onEditorStateChange={this.onEditorStateChange}
                        
                        // 自定义上传图片
                        toolbar={{
                            image: { uploadCallback: this.uploadImageCallBack, alt: { present: true, mandatory: true } },
                        }}
                    />
                </div>
            );
        }
    }
    

    9、父组件获得子组件的值

     
    /**
     * 父组件
     */
    constructor(props) {
            super(props)
    
            // 创建容器
            this.pw = React.createRef()
            this.editor = React.createRef()
        }
    
    const imgs = this.pw.current.getImgs()
    
    
    /**
     * 子组件
     */
    static propTypes = {
        detail: PropTypes.string
    }
    
    getDetail = () => {
        // 返回对应数据的html格式文本
        return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))
    }
    

    六、角色管理

    1、含单选按钮的表格

    <Table
        bordered
        rowKey='_id'
        dataSource={roles}
        columns={this.columns}
        pagination={{
            defaultPageSize: PAGE_SIZE,
        }}
        rowSelection={{
            type: 'radio',
                selectedRowKeys: [role._id],
                    onSelect: (role) => {
                        this.setState({
                            role
                        })
                    }
        }}
        onRow={this.onRow}
        />
    
     onRow = (role) => {
            return {
                onClick: event => {// 点击行
                    console.log('onRow()', role)
                    this.setState({
                        role
                    })
    
                },
            }
        }
    

    相关文章

      网友评论

          本文标题:【教程】react实战

          本文链接:https://www.haomeiwen.com/subject/kzwuictx.html