React+FluentUI实现导航栏和页面跳转

React+FluentUI实现导航栏和页面跳转

大多数企业级应用后台都有个菜单栏,如图:

在这里插入图片描述

路由和跳转页面搭建好以后整个框架雏形就出来了,接下来我们介绍一下路由页面跳转的设计思路;

需求:

  • 此案例分为一级菜单和二级菜单,一级菜单分可展开多项子菜单和不可展开的一级跳转菜单;学生页和老师页两个一级菜单可以展开和闭合,并记录状态,点击学生1,学生2,老师1,老师2子页面,页面跳转,闭合状态不重置,页面不整体刷新,只是页面内容被刷新。

实现思路:

  • 左侧导航栏菜单作为父级页面,页面具体内容作为子页面,每次跳转父页面不变,子页面替换;
  • 左侧导航栏菜单抽象出一个数组,方便以后维护,此数组只记录菜单节点内容,不应有其他逻辑;
  • 一级菜单闭合状态由useState记录,每次点击更新state,遍历路由后由一级菜单state重新赋值再传给解析路由,实现闭合状态记录;

具体代码

//C:\Code\ReactCode\react-demo-ts-menu\src\routes.tsx
import Layout from './layouts/index';
import Stu1 from './pages/student/stu1';
import Stu2 from './pages/student/stu2';
// import Student from './pages/student/student';

import Teacher from './pages/teacher/teacher';
import Teacher1 from './pages/teacher/teacher1';
import Teacher2 from './pages/teacher/teacher2';

import { NavFabricDemoAppExample, routeConfig } from './nav';

const routes: routeConfig[] = [
    {
        path: '/',
        name:'首页',
        component: Layout,
        key: '/',
        icon: 'Home',
        exact: true
    },
    {
        path: '/school',
        name:'学生页',
        icon: 'BulletedList',
        // key: 'student',
        isExpanded: true,
        component: Teacher,
        children: [
            {
                path: '/school/stu1',
                name: '学生1',
                component: Stu1
            },
            {
                path: '/school/stu2',
                name: '学生2',
                component: Stu2
            }
        ]
    },
    {
        path: '/school',
        name: '老师页',
        icon: 'BulletedList',
        // key: 'teacher',
        component: Teacher,
        isExpanded: true,
       
        children: [
            {
                path: '/school/teacher1',
                name: '老师1',
                component: Teacher1
            },
            {
                path: '/school/teacher2',
                name: '老师2',
                component: Teacher2
            },

        ]
    },
    {
        path: '/school/about',
        name: '关于我们',
        icon: 'ContactInfo',
        // isExpanded: true,
        key: '/school/about',
        component: NavFabricDemoAppExample,
        exact: true,
    },


]
export { routes };

//C:\Code\ReactCode\react-demo-ts-menu\src\nav.tsx
import * as React from 'react';
import { Nav, INavLink, INavStyles, INavLinkGroup } from 'office-ui-fabric-react/lib/Nav';
import { initializeIcons } from '@uifabric/icons';
import { routes } from './routes';
import { withRouter } from 'react-router-dom';
import { useState } from 'react';

initializeIcons();

const navStyles: Partial<INavStyles> = { root: { width: 300 } };

export interface expandedModel {
    isExpanded: boolean;
    key: string;
}

// const [isExpandedState, setIsExpandedState] = useState<expandedModel>({ isExpanded: true, key: "student" });

// const [stuIsExpanded, setStuIsExpanded] = useState(true);
// const [teacherIsExpanded, setTeacherIsExpanded] = useState(true);


// const navLinkGroups: INavLinkGroup[] = [
//     {
//         links: [
//             {
//               name: 'Home',
//               url: 'http://example.com',
//             //   expandAriaLabel: 'Expand Home section',
//             //   collapseAriaLabel: 'Collapse Home section',
//               links: [
//                 {
//                   name: 'Activity',
//                   url: 'http://msn.com',
//                   key: 'key1',
//                   target: '_blank',
//                 },
//                 {
//                   name: 'MSN',
//                   url: 'http://msn.com',
//                   disabled: true,
//                   key: 'key2',
//                   target: '_blank',
//                 },
//               ],
//               isExpanded: true,
//             },
//             {
//               name: 'Documents',
//               url: 'http://example.com',
//               key: 'key3',
//               isExpanded: true,
//               target: '_blank',
//             },
//             {
//               name: 'Pages',
//               url: 'http://msn.com',
//               key: 'key4',
//               target: '_blank',
//             },
//             {
//               name: 'Notebook',
//               url: 'http://msn.com',
//               key: 'key5',
//               disabled: true,
//             },
//             {
//               name: 'Communication and Media',
//               url: 'http://msn.com',
//               key: 'key6',
//               target: '_blank',
//             },
//             {
//               name: 'News',
//               url: 'http://cnn.com',
//               icon: 'News',
//               key: 'key7',
//               target: '_blank',
//             },
//           ],
//     },

// ];

export interface routeConfig {
    path: string;
    name: string;
    key?: string;
    children?: routeConfig[];
    component?: any;
    hide?: boolean;
    exact?: boolean;
    icon?: string;
    isExpanded?: boolean;
}

const ParseRoute = (route: routeConfig[]): INavLinkGroup => {
    let navLinkGroupArr: INavLinkGroup = { links: [] };
    // eslint-disable-next-line
    routes.map((item: routeConfig, index: number) => {
        const parent_link: INavLink = { name: '', url: '' };
        const child_links: INavLink[] = [];
        if (item.children !== undefined && item.children !== null) {
            // eslint-disable-next-line
            item.children.map((iitem: any, index: number) => {
                const navlink: INavLink = { url: '', key: '', name: '' };
                navlink.url = iitem.path;
                navlink.name = iitem.name;
                navlink.key = iitem.path;
                navlink.icon = 'News';
                // navlink.isExpanded = true;
                child_links.push(navlink);
                parent_link.links = child_links;
            })
        }

        parent_link.icon = item.icon;
        // parent_link.icon = 'Brightness';
        parent_link.name = item.name;
        parent_link.key = item.key;
        parent_link.path = item.path;
        parent_link.isExpanded = item.isExpanded == null ? true : item.isExpanded;
        navLinkGroupArr.links.push(parent_link);
    })
    return navLinkGroupArr;
}


export class NavFabricDemoAppExample extends React.Component<any, any> {
    navwithRouter = withRouter(({ history }) => {
        const [isExpandedStuState, setIsExpandedStuState] = useState<boolean>(true);
        const [isExpandedTeacherState, setIsExpandedTeacherState] = useState<boolean>(true);
        const route: routeConfig[] = routes;

        // eslint-disable-next-line
        route.map((item: routeConfig, index: number) => {
            if (item.name === '学生页') {
                item.isExpanded = isExpandedStuState;
            }
            if (item.name === '老师页') {
                item.isExpanded = isExpandedTeacherState;
            }
        })

        let groups = ParseRoute(route);
        // console.log(groups)
        return (
            <Nav
                groups={[groups]}
                onLinkClick={
                    (ev?: React.MouseEvent<HTMLElement>, item?: INavLink) => {
                        if (ev) {
                            ev.nativeEvent.preventDefault();
                        }
                        // console.log("item.path: " + item?.path);

                        if (item && item.name === '学生页') {
                            // alert('学生页 link clicked');
                            setIsExpandedStuState(!isExpandedStuState);
                        }
                        if (item && item.name === '老师页') {
                            // alert('学生页 link clicked');
                            setIsExpandedTeacherState(!isExpandedTeacherState);
                        }

                        if (item && item.key) {
                            history.push(item.key);
                            // console.log("item.key: " + item.key);
                            // console.log("history: " + history.location.pathname);
                        }
                      
                    }
                }
                selectedKey={
                    history.location.pathname
                }
                ariaLabel="Nav basic example"
                styles={navStyles}
            />
        );


    });

    render() {
        return (<div className='sidebar'><this.navwithRouter /></div>);
    }

};

//C:\Code\ReactCode\react-demo-ts-menu\src\pages\teacher\teacher.tsx
import * as React from 'react';
import { renderRoutes } from 'react-router-config';

// import { Link } from 'react-router-dom';
import { routes } from '../../routes';
import { NavFabricDemoAppExample, routeConfig } from '../../nav';

export interface Props {
    name: string;
    enthusiasmLevel?: number;
    route: routeConfig[];
}

function Teacher({ name = 'chunyu, this is the parent page', enthusiasmLevel = 1, route = routes }: Props)   {
    if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
    }
    // console.log("route in teacher page: " + routes[1].children);

    return (
        <div className="hello">
            <NavFabricDemoAppExample></NavFabricDemoAppExample>
            
            {/* {renderRoutes(route.children)} */}

            {renderRoutes(routes[1].children)}
            {renderRoutes(routes[2].children)}

            {/* {renderChildRoutes()} */}

            <div className="greeting">
                Hello {name + getExclamationMarks(enthusiasmLevel)}
            </div>
        </div>
    );
}

export default Teacher;

// helpers

function getExclamationMarks(numChars: number) {
    return Array(numChars + 1).join('!');
}

//why can not foreach route to render.
// function renderChildRoutes() {
//     routes.map((item: routeConfig, index: number) => {
//         if (item.children && item.children !== null && item.children !== undefined) {
//             renderRoutes(item.children)
//             console.log("....." + item.children);
//         }
//     })
//     return null;
// }
//C:\Code\ReactCode\react-demo-ts-menu\src\pages\teacher\teacher1.tsx
import * as React from 'react';
// import { renderRoutes } from 'react-router-config';
// import { NavFabricDemoAppExample } from '../../nav';

export interface Props {
    name: string;
    enthusiasmLevel?: number;
}

function Teacher1({ name = 'teacher1...', enthusiasmLevel = 1 }: Props) {
    if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
        <div className="hello">
            <div className="greeting">
                Hello {name + getExclamationMarks(enthusiasmLevel)}
            </div>
        </div>
    );
}

export default Teacher1;

// helpers

function getExclamationMarks(numChars: number) {
    return Array(numChars + 1).join('!');
}
//C:\Code\ReactCode\react-demo-ts-menu\src\pages\teacher\teacher2.tsx
import * as React from 'react';
// import { NavFabricDemoAppExample } from '../../nav';

export interface Props {
    name: string;
    enthusiasmLevel?: number;
}

function Teacher2({ name = 'teacher222...', enthusiasmLevel = 1 }: Props) {
    if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
    }

    return (
        <div className="hello">
            <div className="greeting">
                Hello {name + getExclamationMarks(enthusiasmLevel)}
            </div>
        </div>
    );
}

export default Teacher2;

// helpers

function getExclamationMarks(numChars: number) {
    return Array(numChars + 1).join('!');
}
//C:\Code\ReactCode\react-demo-ts-menu\src\pages\student\stu1.js
import React from 'react';
// import { renderRoutes } from 'react-router-config';

export default class Stu1 extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            route: props.route,
        }
        // console.log(props);
    }
    render() {
        // const route = this.state.route;

        // console.log(route);

        const name = 'Stu1 Page.';
        return <div>
            {/* {renderRoutes(route.children)} */}
            Hello {name}
        </div>;
    }

}

//C:\Code\ReactCode\react-demo-ts-menu\src\pages\student\stu2.js
import React from 'react';
// import { renderRoutes } from 'react-router-config';

export default class Stu2 extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            route: props.route,
        }
        // console.log(props);
    }
    render() {
        // const route = this.state.route;

        // console.log(route);

        const name = 'Stu2 Page.';
        return <div>
            {/* {renderRoutes(route.children)} */}
            Hello {name}
        </div>;
    }

}

以上附上路由设计核心代码和学生老师父子页面;

参考说明:
路由是依赖react-router-dom包的

UI是微软的Fluent UI又叫Fabric,之所以选择这个原因有两点,它没有封装太多冗余样式和功能,运行起来不是很慢,第二,提供丰富微软风格的UI界面,只是有好多插件需要自己实现,倒也很灵活;

Fluent UI官网点这里

仅供学习参考,如有侵权联系我删除

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,react-marknavbar和Anchor都是React组件,可以一起使用来实现页面内的导航栏和锚点跳转。 首先,您需要安装这两个组件,可以使用npm或yarn进行安装。例如,使用npm安装: ``` npm install react-marknavbar anchor-js ``` 安装完成后,您可以在React项目中引入这两个组件: ```javascript import React from 'react'; import { MarkNav, Anchor } from 'react-marknavbar'; import 'anchor-js'; const App = () => { return ( <div> <MarkNav> <Anchor href="#section1">Section 1</Anchor> <Anchor href="#section2">Section 2</Anchor> <Anchor href="#section3">Section 3</Anchor> </MarkNav> <div id="section1"> <h2>Section 1</h2> <p>Content for section 1 goes here.</p> </div> <div id="section2"> <h2>Section 2</h2> <p>Content for section 2 goes here.</p> </div> <div id="section3"> <h2>Section 3</h2> <<p>Content for section 3 goes here.</p> </div> </div> ); }; export default App; ``` 在上面的代码中,我们首先引入了MarkNav和Anchor组件,然后使用MarkNav组件包裹了几个Anchor组件,这些Anchor组件分别对应页面中的几个锚点。在每个锚点的href属性中,我们使用了类似于#section1的语法来指定跳转的目标位置。最后,我们在页面中添加了几个div元素,这些元素分别对应了不同的锚点位置。 当用户在页面上点击导航栏中的链接时,页面会自动滚动到对应的锚点位置。这一过程是通过Anchor组件中的anchor-js库来实现的。如果您需要更多关于这些组件的信息,可以参考官方文档或者相关的开发社区。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值