一步一步构件react后台系统 9 之 tab切换

2018-10-09  本文已影响0人  Biao_349d

一步一步构件react后台系统 9 之 tab切换

先看看效果

可以看到, 在面包屑下面多了个tab栏

tabs.png

思路

之前看到别人的后台系统, 有一个tab, 来记录所有跳转过的页面, 我就在想, 该怎么做?

然后看了一下别人的代码。
原理是这样的。

做一个tab, 然后点击导航的时候, 我们的props就会发生改变, componentWillReceiveProps 方法就会被调用, 然后就在里面监听, 判断当前页面在不在tab面板里面, 如果不在,则拿到当前页面路径到路由表里面进行寻找, 找到对应的对象并提取出来, 然后加入到所有tabs数组里面, 并在组件里面进行渲染。

在路由配置里面, 有component属性的, 里面保存着组件, 直接把这个渲染在tab的面板里面就可以了。

开始动手

  1. 先贴出components/tabs.js完整代码
import React, { Component } from 'react';
import { Tabs  } from 'antd';
import { filterData, deleObj, deepFlatten, removeArrItem } from '@/utils/index.js'
import { main as mainConfig } from '@/router/index'
import {connect} from "react-redux";
import {withRouter} from "react-router-dom";
import {crumbsMap} from "@/reducer/connect";

const TabPane  = Tabs.TabPane;
class MyTabs extends Component {
    constructor(props) {
        super(props)
        this.state = {
            currentPage: {},
            openPages: [],
            routerConfig: deepFlatten(mainConfig),
            mode: 'top',
        }
    }
    handleModeChange = () => {
        let mode = 'left';
        switch (this.state.mode) {
            case 'top':
                mode = 'left';
                break;
            case 'left' :
                mode = 'right';
                break;
            case 'right':
                    mode = 'bottom';
                    break;
            default:
                mode = 'top'
        }
        this.setState({ mode });
    }
    onEdit = (targetKey, action) => {
        if (action === 'remove') {
            this.removeTabs(targetKey)
        }
    }

    removeTabs = (targetKey) => {
        let openPages = removeArrItem(this.state.openPages, function(item){
            return item.path === targetKey
        })
        this.setState({
            openPages
        })
    }
    onTabClick = (activeKey) => {

    }
    componentDidMount () {
    }
    // props 更新时调用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
    addRouteToAllTabPans (props) {
        if (props.location.pathname !== this.state.currentPage.path && props.location.pathname !=='/index') {
            let {openPages} = this.state
            let isHasRoute = openPages.some(item =>item.path === props.location.pathname)
            if (!isHasRoute) {
                let currentRoute = this.getCurrentRoute(props.location.pathname)
                if (currentRoute) {
                    openPages.push(currentRoute)
                    this.setState({
                        openPages,
                        currentPage: currentRoute
                    })
                }
            }
        }
    }
    getCurrentRoute (path) {
        return this.state.routerConfig.filter(item => {
            if (item.path === path) return item
        })[0]
    }
    render() {
        let { location, getRouterConfig, routerConfig, routes } = this.props
        return (
            <div>
                <Tabs
                    hideAdd
                    defaultActiveKey={this.state.currentPage.path}
                    type="editable-card"
                    animated={true}
                    onEdit={this.onEdit}
                    onTabClick={this.onTabClick}
                    tabBarGutter={5}
                    hideAdd={true}
                    tabPosition={this.state.mode}
                    tabBarExtraContent={<span onClick={this.handleModeChange}>{this.state.mode}</span>}
                >
                    {this.state.openPages.map(page => {
                         return <TabPane forceRender tab={page.name} closable={page.closable} key={page.path}>
                            <page.component routes={routes}></page.component>
                        </TabPane>
                    })}
                </Tabs>
            </div>
        );
    }
}

export default connect(crumbsMap.mapStateToProps, crumbsMap.mapDispatchToProps)(withRouter(MyTabs))
  1. 查看细节
    // props 更新时调用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
    addRouteToAllTabPans (props) {
        if (props.location.pathname !== this.state.currentPage.path && props.location.pathname !=='/index') {
            let {openPages} = this.state
            let isHasRoute = openPages.some(item =>item.path === props.location.pathname)
            if (!isHasRoute) {
                let currentRoute = this.getCurrentRoute(props.location.pathname)
                if (currentRoute) {
                    openPages.push(currentRoute)
                    this.setState({
                        openPages,
                        currentPage: currentRoute
                    })
                }
            }
        }
    }
 {this.state.openPages.map(page => {
                         return <TabPane forceRender tab={page.name} closable={page.closable} key={page.path}>
                            <page.component routes={routes}></page.component>
                        </TabPane>
                    })}

大概完成思路就是这样。

完善

这里我把切换功能放在了header上面。 可以根据自己的选择需要或不需要tab

  1. 修改components/header.js

因为menu的点击事件放在了menu标签上面, 而点击的时候, 可以拿到menuItem中的key
因此, 我们进行判断, 如果是tabs, 我们就触发事件

    handleClick = (e) => {
        if (e.key === 'tabs') {
            this.changeTabs()
        } else {
            this.setState({
                current: e.key,
            });
        }
    }

切换 tabs状态

    changeTabs = (obj) => {
        this.props.toggleTabs({
                tabs: !this.props.headerData.tabs
            })
    }

我们的tabs需要在header组件与 index组件里面使用, 因此不能单纯的只是放在当前页面, 需要用redux进行管理。

  1. 修改 reducer/connect

修改 mapLogout,

export const mapLogout = {
    mapStateToProps: (state) => {
        return { headerData: { }, ...state.slidecollapsed}
    },
    mapDispatchToProps: (dispatch) => {
        return {onSlidecollapsed: () => dispatch(action_slidecollapsed), getRouterConfig: () => {
                return dispatch(routerConfig)
            }, toggleSlide: () => {
                dispatch({type: action_slidecollapsed.type})
            },
            onLogout: (data) => {
                return dispatch(fetchPosts('/logout', action_slidecollapsed.type, 'logoutData', data))
            },
            toggleTabs: (data) => {  // 添加切换tab的函数
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}

修改rudexs.js
以前的数据, 是用redux里面控制, 现在我们要改成传参的形式。

const slidecollapsedFuc = (state = { slidecollapsed: false }, action) => {
    switch (action.type) {
        case SLIDECOLLAPSED:
            return Object.assign({}, state, action)
        default:
            return state
    }
}

components/header
添加 changeTabs 方法 。 render 里面引入 headerData 数据, 然后添加MenuItem标签

    changeTabs = (obj) => {
        this.props.toggleTabs({
                tabs: !this.props.headerData.tabs
            })
    }



            let { slidecollapsed, headerData, toggleSlide, toggleTabs } = this.props
            let { tabs } = headerData

                   <Menu.Item key="tabs">
                                            <Icon type="notification" /> {tabs ? '隐藏tabs' : '显示tabs'}
                                    </Menu.Item>

这样, 点击头部的'隐藏tabs'按钮的时候,就会进行切换,文字。

import MyTabs  from '@/components/tabs.js'
import MySlider  from '@/components/slider'
import { connect  } from 'react-redux'
import { mapIndex } from '@/reducer/connect'

state = {
        currentPage: '',
        openPages: []
    }
    onEdit = (targetKey, action) => {
        this[action](targetKey);
    }
    onTabClick = (activeKey) => {
        // if (activeKey !== this.state.currentPage && activeKey === 'home') {
        //     this.props.history.push('/app/home');
        //     return;
        // }
        // if (activeKey !== this.state.currentPage) {
        //     this.props.history.push(MenuToRouter[activeKey]);
        // }
    }

            let { routes, headerData } = this.props
            console.log(this.props)
            let { tabs } = headerData


                                { tabs &&
                                <MyTabs routes={routes}></MyTabs>
                                }
                                {!tabs &&
                                <MyMain routes={routes}></MyMain>
                                }

                                export default connect(mapIndex.mapStateToProps)(Index);
export const mapIndex = {
    mapStateToProps: (state) => {
        return { headerData: { }, ...state.slidecollapsed}
    },
    mapDispatchToProps: (dispatch) => {
        return {
            toggleTabs: (data) => {
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}

先从面包屑组件里面提取 deepFlatten 到公共方法, 然后 添加removeArrItem


export const deepFlatten = arr => [].concat(...arr.map(v => Array.isArray(v) ? deepFlatten(v) : ( typeof v === 'object' ? (Array.isArray(v.routes) ? deepFlatten(v.routes.concat(deleObj(v, 'routes'))) : v) : v )));

// 删除数组某项元素

export const removeArrItem = (arr, validFunx) => {
    arr.splice(arr.findIndex(item => validFunx(item)), 1)
    return arr
}
上一篇下一篇

猜你喜欢

热点阅读