美文网首页
一步一步构件react后台系统 9 之 tab切换

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

作者: Biao_349d | 来源:发表于2018-10-09 13:49 被阅读0次

一步一步构件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 改变的时候, 会触发
    // props 更新时调用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
  • 判断 点击的是当前tab页面, 则不做反应, 如果点击的是index页面, 同样不做反应。
  • 使用some判断当前路径是否已经存在 openPages(tabs数组)里面,
  • 如果不存在, 从路由表里面提取出改路径的路由对象, 并添加到openPages里面。 同时修改 currentPage当前路由对象
    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
                    })
                }
            }
        }
    }
  • 在这里, 就是循环渲染了。
  • <page.component routes={routes}></page.component> 这个就是路由对象的组件, 直接在TabPane里面渲染出来。
 {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'按钮的时候,就会进行切换,文字。

  • 修改views/index/index
    添加引入
    添加方法和属性
    添加this.props.headerData
    判断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);
  • redcer/connect
    添加 mapIndex方法。
export const mapIndex = {
    mapStateToProps: (state) => {
        return { headerData: { }, ...state.slidecollapsed}
    },
    mapDispatchToProps: (dispatch) => {
        return {
            toggleTabs: (data) => {
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}
  • utils/index 内添加方法

先从面包屑组件里面提取 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
}

相关文章

网友评论

      本文标题:一步一步构件react后台系统 9 之 tab切换

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