zoukankan      html  css  js  c++  java
  • 博客管理系统开发 -- 顶部导航栏开发

    这一节我将带领大家,来开发博客管理系统的顶部导航功能,其效果最终如下:

    一、路由优化

    上一节我们介绍了routes的目录结构,其中我们创建了一web.js文件,用来保存路由配置。

    /**
     * web路由配置项
     * @author zy
     * @date 2020/4/5
     */
    export default {
        path: '/',
        name: 'home',
        component: Home,
        exact: false,
        childRoutes: [
            {path: 'about', component: About},
            {path: '*', component: PageNotFound}
        ]
    }

    我们每添加一个路由,都需要在这里配置一次,如果路由较多,且存在多层父子关系的情况下,该配置会越来越复杂且结构不够清晰,此外多个人同时开发,每个人都可能去配置路由信息,那样我们就需要解决代码冲突问题。

    那么有没有一种简单的方式,我们可以根据views文件夹结构动态生成路由配置信息,当然可以了,我们现在就来介绍。

    1、views目录创建

    我们的博客管理系统设计主要参考了郭大大这位博主的文章

    首先来看一下我们博客系统的主页,我们可以看到导航栏主要包含首页、归档、分类、关于这几个菜单,每个菜单项都对应着一个路由。

    因此,我们在views文件夹下创建个web文件夹,并在该文件夹下创建与这几个对应的文件夹,用于保存每个页面对应的视图组件。最终我们views目录结构如下:

    其中menu_config.js用于导出菜单配置,我们后面会根据该配置动态生成路由配置信息以及菜单配置信息。

    我们首先来看一些about文件夹:

    about.jsx:

    import React from 'react';
    
    function About(props) {
        console.log('About=>', props);
        return <h2>About</h2>;
    }
    
    export default About;

    menu_config.js:

    import {UserOutlined} from '@ant-design/icons';
    import About from './about';
    
    export default {
        title: '关于',
        icon: UserOutlined,
        path: 'about',
        component: About
    }

    path:指定About组件的路由,由于每个路由实际上都对应这一个菜单项,因此我们通过icon指定菜单图标,title指定菜单名称。

    如果一个菜单还有子菜单,比如归档菜单下面还有子菜单github:

     archives文件夹下menu_config.js:

    import {FolderOutlined} from '@ant-design/icons';
    import github from './github/menu_config';
    
    export default {
        title: '归档',
        icon: FolderOutlined,
        path: 'archives',
        subMenus: [github]
    }

    这里我们配置了归档的子菜单github;

    github.jsx:

    import React from 'react';
    
    function Github(props) {
        console.log('Github=>', props);
        return <h2>github</h2>;
    }
    
    export default Github;

    menu_config.js:

    import {GithubOutlined} from '@ant-design/icons';
    import Github from './github';
    
    export default {
        title: 'github',
        icon: GithubOutlined,
        path: 'github',
        component: Github
    }

    home和categories文件夹同上,就不一一介绍,最后我们通过web文件夹下的menu_config导出该菜单结构:

    import home from './home/menu_config';
    import archives from './archives/menu_config';
    import categories from './categories/menu_config';
    import about from './about/menu_config';
    export default [home, archives, categories, about];

    2、获取菜单配置信息

    由于我们之前配置的path都是相对路径,因此我们需要将其转换为绝对路径,此外,我们还在菜单配置中加入了404菜单配置项;

    @/components/404/menu_config:

    import PageNotFound from './index';
    
    export default {
        title: '404',
        icon: '',
        path: '*',
        component: PageNotFound,
        invisible: true
    }

    这里配置了invisible指明*路由不需要出现在菜单项中。

    utils/get_menus.js:

    import _ from 'lodash';
    import pageNotFoundMenu from '@/components/404/menu_config';
    
    /**
     * 解析menu_config 将配置路径由相对路径转为绝对路径
     * @author zy
     * @date 2020/4/8
     * @param menus:menu_config配置
     * @return contextPath:设置根路径 
     */
    const getMenus = (menus, contextPath) => {
        const menusCopy = _.cloneDeep(menus);
    
        const decodeMenus = (menusCopy, menuContextPath) => {
            _.forEach(menusCopy, item => {
                //获取当前菜单路径
                let path = item.path ? `${menuContextPath}/${item.path}` : menuContextPath;
                item.path = path.replace(//+/g, '/');
                if (item.subMenus) {
                    decodeMenus(item.subMenus, path);
                }
            })
    
            //给每个同阶菜单追加一个404   如/* /archives/* /archives/layout/*
            if (menusCopy) {
                const menu = _.cloneDeep(pageNotFoundMenu);
                menu.path = (menuContextPath + '/*').replace(//+/g, '/');
                menusCopy.push(menu);
            }
        }
    
        decodeMenus(menusCopy, contextPath);
        return menusCopy;
    }
    
    export default getMenus;

    utils/index.js:

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: 统用函数
     */
    import getMenusFunctions from './get_menus';
    
    export const getMenus = getMenusFunctions;

    3、routes目录

    我们修改routes/web.js

    /**
     * @author zy
     * @date 2020/4/5
     * @Description: web路由
     * 不懂的可以参考:https://segmentfault.com/a/1190000020812860
     * https://reacttraining.com/react-router/web/api/Route
     */
    import Layout from '@/layout/web';
    import menus from '@/views/web/menu_config';
    import {getMenus} from '@/utils';
    import {WEB_ROOT_PATH} from '@/config';
    
    /**
     * web路由配置项
     * @author zy
     * @date 2020/4/5
     */
    
    //web 菜单配置
    export const webMenuConfig = getMenus(menus, WEB_ROOT_PATH);
    
    //web route配置   
    export const webRouteConfig = {
        title: 'home',
        path: WEB_ROOT_PATH,
        component: Layout,                      //根路径下配置web统一布局样式
        subMenus: webMenuConfig
    }

    这里WEB_ROOT_PATH配置为'/'路径:

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: 项目配置文件
     */
    //web 根路径
    export const WEB_ROOT_PATH = '/';
    
    //导航栏博客名称
    export const HEADER_BLOG_NAME = '我的博客';

    其对应的组件为Layout,该组件是我们的布局组件,其主要包括顶部导航和侧边导航部分。

    修改routes/index.js:

    /**
     * @author zy
     * @date 2020/4/5
     * @Description: 路由组件
     */
    import React from 'react';
    import { Switch, Route } from 'react-router-dom';
    import { webRouteConfig } from './web';
    import _ from 'lodash';
    
    
    //保存所有路由配置的数组
    const routeConfig = [webRouteConfig]
    
    /**
     * 路由配置
     * @author zy
     * @date 2020/4/5
     */
    export default function () {
    
        /**
         * 生成路由嵌套结构
         * @author: zy
         * @date: 2020-03-05
         * @param routeConfig: 路由配置数组
         */
        const renderRouters = (routeConfig) => {
            const routes = [];
    
            //遍历每一个路由项
            _.forEach(routeConfig, item => {
                //这里使用了嵌套路由
                routes.push(
                    <Route
                        key={item.path}
                        path={item.path}
                        component={() =>
                            <div className={item.title}>
                                {item.component && <item.component />}
                                {item.subMenus && renderRouters(item.subMenus)}
                            </div>
                        }
                        exact={item.subMenus ? false : true}
                    />
                );
            });
    
            return <Switch>{routes}</Switch>;
        };
    
        return renderRouters(routeConfig);
    }

    我们可以输出webRouteConfig:

    二、Layout组件开发

    上面我们已经说过WEB_ROOT_PATH路由,对应的组件为Layout,其中主要包括顶部导航和侧边导航部分,这里我们将尝试开发顶部导航功能:

    我们将顶部导航拆分为两个组件Left,Right;Right组件拆分为Serch、NavBar、UserInfo三个组件;

    我们在layout下创建web文件夹,其目录如下:

    1、web/index.js

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: web页面布局
     */
    import React from 'react';
    import {Layout, Row, Col} from 'antd';
    import Header from './header';
    
    // 响应式
    const siderLayout = {xxl: 4, xl: 5, lg: 5, sm: 0, xs: 0}
    const contentLayout = {xxl: 20, xl: 19, lg: 19, sm: 24, xs: 24}
    
    
    /**
     * Web布局组件
     * @author zy
     * @date 2020/4/6
     */
    const WebLayout = props => {
        return (
            <Layout >
                <Header/>
                <Row>
                    <Col {...siderLayout}>
    
                    </Col>
    
                    <Col {...contentLayout}>
    
                    </Col>
                </Row>   </Layout>
        )
    }
    
    export default WebLayout;

    2、web/header/index.js

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: web 头部布局
     */
    import React from 'react';
    import {Layout, Row, Col} from 'antd';
    import Left from './left';
    import Right from './right';
    import styles from './styles.scss';
    
    const Header = Layout.Header;
    
    /**
     * 头部布局组件
     * @author zy
     * @date 2020/4/6
     */
    const WebHeader = () => {
        // 响应式  xxl:超大屏 一行显示24/4列  xl:大屏一行显示24/5 ...
        const responsiveLeft = {xxl: 4, xl: 5, lg: 5, sm: 4, xs: 24};
        const responsiveRight = {xxl: 20, xl: 19, lg: 19, sm: 20, xs: 0};
    
        return (
            <Header id='app-header' className={styles.appHeader}>
                <Row>
                    <Col {...responsiveLeft}>
                        <Left/>
                    </Col>
                    <Col {...responsiveRight}>
                        <Right/>
                    </Col>
                </Row>
            </Header>
        )
    }
    
    export default WebHeader;

    2、web/header/styles.scss

    @import '@/styles/other.scss';
    
    .appHeader {
      padding: 0;
      background: #fff;
      box-shadow: 0 2px 8px $headerBoxShadowColor;
    }

    这里我们引入了@/styles/other.scss文件:

    /**
     * @author zy
     * @date 2020/4/7
     * @Description: 所有颜色定义
     */
    
    //头部颜色
    $headerColor: rgba(0, 0, 0, .85);
    $headerBoxShadowColor: #f0f1f2;
    
    
    //分割线颜色
    $dividerColor: rgb(235, 237, 240);
    
    //图标颜色
    $searchIconColor: #ced4d9;
    
    //占位符颜色
    $placeholderColor: #a3b1bf;
    
    
    //主页颜色
    $homeBasicColor: #0cb7d5;

    三、Left组件

    1、index.js

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: 头部左侧布局
     */
    import React from 'react';
    import {DingdingOutlined} from '@ant-design/icons';
    import styles from './styles.scss';
    import {HEADER_BLOG_NAME} from '@/config'
    
    /**
     * 头部左侧布局组件
     * @author zy
     * @date 2020/4/6
     */
    const HeaderLeft = props => {
    
        return (
            <div className={styles.headerLeft}>
                <a href='/' className={styles.blogIcon}>
                    <DingdingOutlined/>{HEADER_BLOG_NAME}
                </a>
            </div>
        )
    }
    
    export default HeaderLeft;

    2、styles.scss

    @import '@/styles/other.scss';
    
    .headerLeft {
      padding-left: 40px;
      font-size: 20px;
      color: $headerColor;
      display: flex;
      align-items: center;
      line-height: 64px;
      height: 64px;
    
      .blogIcon {
        overflow: hidden;
        color: $headerColor;
        font-size: 18px;
        white-space: nowrap;
        text-decoration: none;
    
        span {
          margin-right: 16px;
        }
      }
    }

    四、Right组件

    1、index.jsx

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: 头部右侧布局
     */
    import React from 'react';
    import Search from './right_search';
    import Navbar from './right_nav_bar';
    import UserInfo from './right_user_info';
    import styles from './styles.scss';
    
    /**
     * 头部右侧布局组件
     * @author zy
     * @date 2020/4/6
     */
    const HeaderRight = props => {
        return (
            <div className={styles.headerRight}>
                <Search/>
                <Navbar/>
                <UserInfo/>
            </div>
        )
    }
    
    export default HeaderRight;

    2、right_search.jsx:

    /**
     * @author zy
     * @date 2020/4/6
     * @Description: 文章搜索
     */
    import React from 'react';
    import {useSelector, useDispatch} from 'react-redux';
    import {Input} from 'antd';
    import {SearchOutlined} from '@ant-design/icons';
    import styles from './styles.scss';
    import {setKeyword} from '@/redux/article/actions';
    
    /**
     * 搜索组件
     * @author zy
     * @date 2020/4/6
     */
    function SearchButton(props) {
        //dispatch
        const dispatch = useDispatch()
    
        //将store状态article映射到组件
        const article = useSelector(state => state.article);
    
        //获取文章信息
        const {keyword} = article;
    
        //搜索关键字发生变化
        const handleChange = e => {
            dispatch(setKeyword(e.target.value));
        }
    
        //确定 开始搜索
        const handleSubmit = () => {
            if (keyword) {
                console.log('开始搜索', keyword);
            }
        }
    
        return (
            <div className={styles.searchBox}>
                <SearchOutlined className={styles.searchIcon}/>
                <Input
                    type='text'
                    value={keyword}
                    onChange={handleChange}
                    onPressEnter={handleSubmit}
                    className={styles.searchInput}
                    placeholder='搜索文章'
                    style={{ 200}}
                />
            </div>
        )
    }
    
    export default SearchButton;

    这里使用redux状态管理器保存搜索关键字keyword,当用户在输入框输入搜索内容时,触发onChange事件:

        //搜索关键字发生变化
        const handleChange = e => {
            dispatch(setKeyword(e.target.value));
        }

    此时调用dispatch设置keyword,当我们点击搜索时,这里将会将keyword搜索关键字输出:

        //确定 开始搜索
        const handleSubmit = () => {
            if (keyword) {
                console.log('开始搜索', keyword);
            }
        }

    实际上,我们点搜索的时候,应当使用ajax请求服务器获取文章,然后将其保存到store状态中,然而由于此时没有开发后端接口,所以只好先输出到控制台。

    我们再来看看文章reducer的配置,我们在redux下创建article文件夹:

    (1).actions.js

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 文章action
     */
    import * as TYPES from './types';
    
    //设置搜索关键字
    export const setKeyword = (params) => ({
        type: TYPES.ARTICLE_SET_KEYWORD,
        payload: params
    })

    (2).reducer.js

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 文章reducer
     */
    import * as TYPES from './types';
    
    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 初始化文章信息
     */
    const defaultState = {
        keyword: ''
    }
    
    /**
     * articleReducer
     * @author zy
     * @date 2020/4/12
     */
    export default function articleReducer(state = defaultState, action) {
        const {type, payload} = action
        switch (type) {
            case TYPES.ARTICLE_SET_KEYWORD:
                return {...state, keyword: payload}
    
            default:
                return state
        }
    }

    (3).types.js

    // article
    export const ARTICLE_SET_KEYWORD = '';

    同时redux/root_reducers.js中引入articleReducer:

    /**
     * @author zy
     * @date 2020/4/5
     * @Description: 合并reducer
     */
    import {combineReducers} from 'redux';
    import article from './article/reducer';
    
    export default combineReducers({ article})

    3、right_nav_bar.jsx

    /**
     * @author zy
     * @date 2020/4/7
     * @Description: 导航栏
     */
    import React from 'react';
    import {Link, useLocation} from 'react-router-dom';
    import {Menu} from "antd";
    import {webMenuConfig} from '@/routes/web';
    import _ from 'lodash';
    import styles from './styles.scss';
    
    const {SubMenu} = Menu;
    
    /**
     * 导航栏组件
     * @author zy
     * @date 2020/4/7
     */
    function NavBar(props) {
        //获取当前location对象
        const location = useLocation();
    
        //菜单样式 默认水平
        const {mode = 'horizontal'} = props;
    
        /**
         * 生成菜单树
         * @author zy
         * @date 2020/4/7
         */
        const genMenuTree = (menus) => {
            return _.map(menus, menu => {
                const title = <span>{menu.icon && <menu.icon/>} {menu.title}</span>;
                return menu.subMenus ?
                    !menu.invisible && <SubMenu key={menu.title}
                                                title={title}>{genMenuTree(menu.subMenus)}</SubMenu> :
                    !menu.invisible && <Menu.Item key={menu.path}><Link to={menu.path}>{title}</Link></Menu.Item>;
            })
        }
    
        const onSelect = ({item, key, keyPath, selectedKeys, domEvent}) => {
            console.log('选择项为', selectedKeys);
        }
    
        return (
            <Menu className={styles.headerNav} mode={mode} selectedKeys={[location.pathname]} onSelect={onSelect}>
                {genMenuTree(webMenuConfig)}
            </Menu>
        )
    }
    
    export default NavBar;

    4、right_user_info.jsx

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 用户信息
     */
    import React from 'react';
    import {useSelector, useDispatch} from 'react-redux';
    import {Button, Dropdown, Menu, Avatar} from 'antd';
    import {useBus} from '@/hooks/use_bus';
    import {USER_LOGIN, USER_REGISTER} from '@/redux/user/types';
    import {loginout} from '@/redux/user/actions';
    
    /**
     * 用户细腻些组件
     * @author zy
     * @date 2020/4/12
     */
    function UserInfo(props) {
        //dispatch
        const dispatch = useDispatch()
    
        //将store状态user映射到组件
        const userInfo = useSelector(state => state.user);
    
        //获取用户信息
        const {username} = userInfo;
    
        //使用bus
        const bus = useBus();
    
        //菜单
        const menuOverLay = (
            <Menu>
                <Menu.Item>
                    <span>导入文章</span>
                </Menu.Item>
                <Menu.Item>
                    <span>后台管理</span>
                </Menu.Item>
                <Menu.Item>
                    <span onClick={() => dispatch(loginout())}>退出登录</span>
                </Menu.Item>
            </Menu>
        );
        return (
            <div>
                {/*登录 or not*/}
                {username ? (
                        <Dropdown placement='bottomCenter' overlay={menuOverLay} trigger={['click', 'hover']}>
                            <Avatar size={32}
                                    src='http://img2.imgtn.bdimg.com/it/u=3906498928,936423956&fm=26&gp=0.jpg'>{username}</Avatar>
                        </Dropdown>
                    )
                    : (
                        <div>
                            <Button
                                ghost
                                type='primary'
                                size='small'
                                style={{marginRight: 20}}
                                onClick={e => bus.emit('openSignModal', USER_LOGIN)}>
                                登录
                            </Button>
                            <Button ghost
                                    type='danger'
                                    size='small'
                                    onClick={e => bus.emit('openSignModal', USER_REGISTER)}>
                                注册
                            </Button>
                        </div>
                    )}
            </div>
        )
    }
    
    export default UserInfo;

    这里显示通过useSelector将store中的用户信息映射到当前组件中,如果用户信息存在,则会加载下拉菜单:

    如果用户信息不存在,则会显示:

    这里使用到了事件发生器,当点击了登录时候,会触发openSignModal事件,并传入参数USER_LOGIN:

      onClick={e => bus.emit('openSignModal', USER_LOGIN)}>

    当点击注册的时候,也会触发openSignModal事件,但是传入的参数是USER_REGISTER,当有函数监听了该事件的发生,那么就会执行该监听函数,这里实际上采用的就是是发布/订阅模式。

    我们在components/public下创建public公共组件:

    components/public/index.jsx:

    /**
     * @author zy
     * @date 2020/4/12
     * @Description:  Public 公共组件,挂在在 APP.jsx 中,用于存放初始化的组件/方法 或者公用的 modal 等
     */
    import React from 'react';
    import useMount from '@/hooks/use_mount'
    import SignModal from '@/components/public/sign_modal';
    
    /**
     * 公共组件
     * @author zy
     * @date 2020/4/12
     */
    function PublicComponent(props) {
        useMount(() => {
        })
    
        return (
            <div>
                <SignModal/>
            </div>
        )
    }
    
    export default PublicComponent;

    我们引入了SignModal组件,该组件用于注册/登录,在public下创建sign_modal文件夹,并添加index.jsx文件:

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 注册 or 登录对话框
     */
    import React, {useState} from 'react';
    import {Form, Input, Button, Modal} from 'antd';
    import {UserOutlined, LockOutlined} from '@ant-design/icons';
    import {login, register} from '@/redux/user/actions';
    import {USER_LOGIN} from '@/redux/user/types'
    import {useDispatch} from 'react-redux';
    import {busListener} from '@/hooks/use_bus';
    
    //表单样式调整
    const FormItemLayout = {
        labelCol: {
            xs: {span: 0},
            sm: {span: 5}
        },
        wrapperCol: {
            xs: {span: 24},
            sm: {span: 19}
        }
    }
    
    /**
     * 用户注册 or 登录组件
     * @author zy
     * @date 2020/4/12
     */
    function SignModal(props) {
        //获取表单
        const [form] = Form.useForm();
    
        //获取dispatch
        const dispatch = useDispatch();
    
        //对话框可见?
        const [visible, setVisible] = useState(false)
    
        //类型:登录 or 注册
        const [type, setType] = useState('login')
    
        //事件监听 如果触发登录或者注册事件,显示该对话框
        busListener('openSignModal', type => {
            form.resetFields();
            setType(type);
            setVisible(true);
        })
    
        //提交表单且数据验证成功后回调事件
        const onFinish = values => {
            console.log('Received values of form: ', values);
            const action = type === USER_LOGIN ? login : register;
            dispatch(action(values)).then(() => {
                setVisible(false);
            })
        };
    
        //确认密码
        function compareToFirstPassword(rule, value, callback) {
            if (value && value !== form.getFieldValue('password')) {
                callback('Two passwords that you enter is inconsistent!')
            } else {
                callback()
            }
        }
    
        return (
            <Modal
                width={420}
                title={type === USER_LOGIN ? 'login' : 'register'}
                visible={visible}
                onCancel={e => setVisible(false)}
                footer={null}>
                <Form
                    form={form}
                    name="normal_login"
                    layout="horizontal"
                    onFinish={onFinish}
    
                >
                    {/*登录或注册*/}
                    {type === USER_LOGIN ? (
                            <div>
                                <Form.Item
                                    name="username"
                                    rules={[{required: true, message: 'Please input your Username!'}]}
                                >
                                    <Input prefix={<UserOutlined className="site-form-item-icon"/>} placeholder="Username"/>
                                </Form.Item>
    
                                <Form.Item
                                    name="password"
                                    rules={[{required: true, message: 'Please input your Password!'}]}
                                >
                                    <Input
                                        prefix={<LockOutlined/>}
                                        type="password"
                                        placeholder="Password"
                                    />
                                </Form.Item>
                            </div>
                        )
                        : (
                            <div>
                                <Form.Item
                                    {...FormItemLayout}
                                    label="用户名"
                                    name="username"
                                    rules={[{required: true, message: 'Please input your Username!'}]}
                                >
                                    <Input placeholder="Username"/>
                                </Form.Item>
    
                                <Form.Item
                                    {...FormItemLayout}
                                    label="密码"
                                    name="password"
                                    rules={[{required: true, message: 'Please input your Password!'}]}
                                >
                                    <Input
                                        type="password"
                                        placeholder="Password"
                                    />
                                </Form.Item>
    
                                <Form.Item
                                    {...FormItemLayout}
                                    label="确认密码"
                                    name='confirm'
                                    rules={[
                                        {required: true, message: 'Password is required'},
                                        {validator: compareToFirstPassword}
                                    ]}>
                                    <Input placeholder='Confirm Password' type='password'/>
                                </Form.Item>
    
                                <Form.Item
                                    {...FormItemLayout}
                                    label="邮箱"
                                    name='email'
                                    rules={[
                                        {type: 'email', message: 'The input is not valid E-mail!'},
                                        {required: true, message: 'Please input your E-mail!'}
                                    ]}>
                                    <Input placeholder='Email'/>
                                </Form.Item>
                            </div>
                        )}
                    <Form.Item>
                        <Button type="primary" htmlType="submit" style={{ '100%'}}>
                            Login
                        </Button>
                    </Form.Item>
                </Form>
            </Modal>
        )
    }
    
    export default SignModal;

    这里采用antd表单来实现登录/注册,我们通过busListener监听登录和注册事件:

        //事件监听 如果触发登录或者注册事件,显示该对话框
        busListener('openSignModal', type => {
            form.resetFields();
            setType(type);
            setVisible(true);
        })

    如果是登录,对话框内容如下:

    如果是注册,对话框内容如下:

     以登录为例,当我们输入了登录信息后,如果校验通过,则会执行onFinish函数:

        //提交表单且数据验证成功后回调事件
        const onFinish = values => {
            console.log('Received values of form: ', values);
            const action = type === USER_LOGIN ? login : register;
            dispatch(action(values)).then(() => {
                setVisible(false);
            })
        };

    这里将会调用dispatch用来保存用户信息,然后关闭对话框。

    同文章reducer,我们再来看看用户reducer的实现,我们在redux下创建user文件夹:

    (1).actions.js:

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 用户action
     */
    import * as TYPES from './types'
    
    import {message} from 'antd'
    
    /**
     * 执行登录操作
     * @author zy
     * @date 2020/4/12
     */
    export const login = params => {
        return dispatch => {
            return new Promise((resolve, reject) => {
                //设置用户信息
                dispatch({
                    type: TYPES.USER_LOGIN,
                    payload: params
                })
                message.success(`登录成功, 欢迎您 ${params.username}`);
                resolve('这里调用登录接口');
            })
        }
    }
    
    /**
     * 执行注册操作
     * @author zy
     * @date 2020/4/12
     */
    export const register = params => {
        return dispatch => {
            message.success('注册成功,请重新登录您的账号!')
        }
    }
    
    /**
     * 执行退出登录操作
     * @author zy
     * @date 2020/4/12
     */
    export const loginout = () => ({
        type: TYPES.USER_LOGIN_OUT
    })

    (2).reducer.js:

    /**
     * @author zy
     * @date 2020/4/12
     * @Description: 用户reducer
     */
    import * as TYPES from './types';
    
    /**
     * 初始化用户信息
     * @author zy
     * @date 2020/4/12
     */
    let defaultState = {
        username: '',
        userId: 0,
        github: null
    }
    
    /**
     * userReducer
     * @author zy
     * @date 2020/4/12
     */
    export default function userReducer(state = defaultState, action) {
        const {type, payload} = action
        switch (type) {
            case TYPES.USER_LOGIN:
                const {username, userId, github} = payload;
                return {...state, username, userId, github};
    
            case TYPES.USER_LOGIN_OUT:
                return {...state, username: '', userId: 0, github: null};
    
            default:
                return state;
        }
    }

    (3).type.js

    // user
    export const USER_LOGIN = 'USER_LOGIN';
    export const USER_REGISTER = 'USER_REGISTER';
    export const USER_LOGIN_OUT = 'USER_LOGIN_OUT';

    同时redux/root_reducers.js中引入userReducer:

    /**
     * @author zy
     * @date 2020/4/5
     * @Description: 合并reducer
     */
    import {combineReducers} from 'redux';
    import user from './user/reducer';
    import article from './article/reducer';
    
    export default combineReducers({user, article})

    最后我们需要将PublicComponent挂在到App组件上:

    /**
     * @author zy
     * @date 2020/4/5
     * @Description: 根组件
     */
    import React from 'react';
    import Routes from '@/routes';
    import {BrowserRouter} from 'react-router-dom';
    import PublicComponent from '@/components/public';
    
    export default function App(props) {
        return (
            <BrowserRouter>
                <Routes/>
                <PublicComponent/>
            </BrowserRouter>
        )
    }

     至此,我们这节的内容介绍完毕了,代码有点多,我整理放在github上:https://github.com/Zhengyang550/react-blog-zy

    参考文章:

    [1]郭大大博客系统开发

  • 相关阅读:
    Unity Shader 之 uv动画
    c++源文件后缀名问题
    Unity Shader 之 透明效果
    正则表达式
    Unity Shader基础
    Unity Shader 之 渲染流水线
    2017/11/22 Leetcode 日记
    2017/11/21 Leetcode 日记
    2017/11/13 Leetcode 日记
    2017/11/20 Leetcode 日记
  • 原文地址:https://www.cnblogs.com/zyly/p/12650206.html
Copyright © 2011-2022 走看看