forked from ddrilling/asb_cloud_front
Добавлена возможность отображения крошек
This commit is contained in:
parent
26222104b7
commit
879fea41a5
@ -1,4 +1,4 @@
|
|||||||
import { Button, Layout, LayoutProps, Menu, SiderProps } from 'antd'
|
import { Breadcrumb, Layout, LayoutProps, Menu, SiderProps } from 'antd'
|
||||||
import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
|
import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||||
import { Link, Outlet } from 'react-router-dom'
|
import { Link, Outlet } from 'react-router-dom'
|
||||||
@ -6,19 +6,18 @@ import {
|
|||||||
ApartmentOutlined,
|
ApartmentOutlined,
|
||||||
CodeOutlined,
|
CodeOutlined,
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
MenuFoldOutlined,
|
|
||||||
MenuUnfoldOutlined,
|
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
|
|
||||||
import { LayoutPropsContext } from '@asb/context'
|
import { LayoutPropsContext } from '@asb/context'
|
||||||
import PageHeader from '@components/PageHeader'
|
|
||||||
import { UserMenu, UserMenuProps } from '@components/UserMenu'
|
import { UserMenu, UserMenuProps } from '@components/UserMenu'
|
||||||
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
|
import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector'
|
||||||
import { isURLAvailable, wrapPrivateComponent } from '@utils'
|
import { isURLAvailable, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import SuspenseFallback from './SuspenseFallback'
|
import SuspenseFallback from './SuspenseFallback'
|
||||||
|
|
||||||
|
import Logo from '@images/Logo'
|
||||||
|
|
||||||
import '@styles/layout.less'
|
import '@styles/layout.less'
|
||||||
|
|
||||||
const { Content, Sider } = Layout
|
const { Content, Sider } = Layout
|
||||||
@ -32,6 +31,8 @@ export type LayoutPortalProps = Omit<LayoutProps, 'children'> & {
|
|||||||
siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
|
siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
|
||||||
isAdmin?: boolean
|
isAdmin?: boolean
|
||||||
fallback?: JSX.Element
|
fallback?: JSX.Element
|
||||||
|
breadcrumb?: boolean | JSX.Element
|
||||||
|
topRightBlock?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: LayoutPortalProps = {
|
const defaultProps: LayoutPortalProps = {
|
||||||
@ -49,7 +50,7 @@ const _LayoutPortal = memo(() => {
|
|||||||
const [currentWell, setCurrentWell] = useState<string>('')
|
const [currentWell, setCurrentWell] = useState<string>('')
|
||||||
const [props, setProps] = useState<LayoutPortalProps>(defaultProps)
|
const [props, setProps] = useState<LayoutPortalProps>(defaultProps)
|
||||||
|
|
||||||
const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, ...other } = useMemo(() => props, [props])
|
const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, breadcrumb, topRightBlock, ...other } = useMemo(() => props, [props])
|
||||||
|
|
||||||
const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), [])
|
const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), [])
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ const _LayoutPortal = memo(() => {
|
|||||||
<Sider {...siderProps} collapsedWidth={50} collapsed={menuCollapsed} trigger={null} collapsible className={`menu-sider ${siderProps?.className || ''}`}>
|
<Sider {...siderProps} collapsedWidth={50} collapsed={menuCollapsed} trigger={null} collapsible className={`menu-sider ${siderProps?.className || ''}`}>
|
||||||
<div className={'sider-content'}>
|
<div className={'sider-content'}>
|
||||||
<button className={'sider-toogle'} onClick={() => setMenuCollapsed((prev) => !prev)}>
|
<button className={'sider-toogle'} onClick={() => setMenuCollapsed((prev) => !prev)}>
|
||||||
{menuCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
<Logo onlyIcon={menuCollapsed} />
|
||||||
</button>
|
</button>
|
||||||
<div className={'scrollable hide-slider'}>
|
<div className={'scrollable hide-slider'}>
|
||||||
{sider}
|
{sider}
|
||||||
@ -91,25 +92,34 @@ const _LayoutPortal = memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
</Sider>
|
</Sider>
|
||||||
)}
|
)}
|
||||||
<Layout className={'page-content'}>
|
{!isAdmin && (
|
||||||
<PageHeader title={title}>
|
|
||||||
{isAdmin ? (
|
|
||||||
<Button size={'large'}>
|
|
||||||
<Link to={'/'}>Вернуться на сайт</Link>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<WellTreeSelector
|
<WellTreeSelector
|
||||||
open={wellsTreeOpen}
|
open={wellsTreeOpen}
|
||||||
onClose={() => setWellsTreeOpen(false)}
|
onClose={() => setWellsTreeOpen(false)}
|
||||||
{...selectorProps}
|
{...selectorProps}
|
||||||
onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')}
|
onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => setWellsTreeOpen((prev) => !prev)}>{currentWell}</Button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
<Layout className={'page-content'}>
|
||||||
<Content {...other} className={`${sheet ? 'site-layout-background sheet' : ''} ${other.className ?? ''}`}>
|
<Content {...other} className={`${sheet ? 'site-layout-background sheet' : ''} ${other.className ?? ''}`}>
|
||||||
|
{(breadcrumb || topRightBlock) && (
|
||||||
|
<div className={'breadcrumb-block'}>
|
||||||
|
{breadcrumb && (
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb.Item href={'/'}>
|
||||||
|
<HomeOutlined />
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
{!isAdmin && (
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<a style={{ userSelect: 'none' }} onClick={() => setWellsTreeOpen((prev) => !prev)}>{currentWell}</a>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
)}
|
||||||
|
{breadcrumb}
|
||||||
|
</Breadcrumb>
|
||||||
|
)}
|
||||||
|
{topRightBlock}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<LayoutPropsContext.Provider value={setLayoutProps}>
|
<LayoutPropsContext.Provider value={setLayoutProps}>
|
||||||
<Suspense fallback={fallback ?? <SuspenseFallback style={{ minHeight: '100%' }} />}>
|
<Suspense fallback={fallback ?? <SuspenseFallback style={{ minHeight: '100%' }} />}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
@ -121,8 +131,6 @@ const _LayoutPortal = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, {
|
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, { requirements: ['Deposit.get'] })
|
||||||
requirements: ['Deposit.get'],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default LayoutPortal
|
export default LayoutPortal
|
||||||
|
56
src/components/MenuBreadcrumb.tsx
Normal file
56
src/components/MenuBreadcrumb.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Breadcrumb, BreadcrumbItemProps } from 'antd'
|
||||||
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
import { memo, useMemo } from 'react'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
import { PrivateWellMenuItem } from '@components/PrivateWellMenu'
|
||||||
|
import { FunctionalValue, useFunctionalValue } from '@utils'
|
||||||
|
|
||||||
|
export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: string[], root: string = '/') => {
|
||||||
|
const out = []
|
||||||
|
const parts = [...pathParts]
|
||||||
|
let route = root
|
||||||
|
let arr: PrivateWellMenuItem[] | undefined = items
|
||||||
|
while (arr && parts.length > 0) {
|
||||||
|
const child: PrivateWellMenuItem | undefined = arr.find(elm => elm.route.toLowerCase() === parts[0].toLowerCase())
|
||||||
|
if (!child) break
|
||||||
|
route = join(route, child.route)
|
||||||
|
out.push({ ...child, route })
|
||||||
|
parts.splice(0, 1)
|
||||||
|
arr = child.children
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuBreadcrumbItemsProps = {
|
||||||
|
menuItems: PrivateWellMenuItem[]
|
||||||
|
pathRoot?: RegExp
|
||||||
|
itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps>
|
||||||
|
itemRender?: (item: PrivateWellMenuItem) => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuBreadcrumbItems = memo<MenuBreadcrumbItemsProps>(({ menuItems, pathRoot = /^\//, itemsProps, itemRender }) => {
|
||||||
|
const location = useLocation()
|
||||||
|
const getItemProps = useFunctionalValue(itemsProps)
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
const path = location.pathname
|
||||||
|
const rootPart = pathRoot.exec(path)
|
||||||
|
if (!rootPart || rootPart.length <= 0) return []
|
||||||
|
const root = rootPart[0]
|
||||||
|
const parts = path.trim().slice(root.length).split('/')
|
||||||
|
return makeBreadcrumbItems(menuItems, parts, root)
|
||||||
|
}, [location, menuItems, pathRoot])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Breadcrumb.Item key={item.route} {...getItemProps(item)}>
|
||||||
|
{itemRender ? itemRender(item) : (
|
||||||
|
<Link to={item.route}>{item.title}</Link>
|
||||||
|
)}
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
@ -1,27 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
import { Layout } from 'antd'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { BasicProps } from 'antd/lib/layout/layout'
|
|
||||||
|
|
||||||
import { headerHeight } from '@utils'
|
|
||||||
|
|
||||||
import Logo from '@images/Logo'
|
|
||||||
|
|
||||||
import '@styles/layout.less'
|
|
||||||
|
|
||||||
export type PageHeaderProps = BasicProps & {
|
|
||||||
title?: string
|
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PageHeader: React.FC<PageHeaderProps> = memo(({ title, children, ...other }) => (
|
|
||||||
<Layout.Header className={'header'} {...other}>
|
|
||||||
<Link to={'/'} style={{ height: headerHeight }}>
|
|
||||||
<Logo />
|
|
||||||
</Link>
|
|
||||||
<h1 className={'title'}>{title}</h1>
|
|
||||||
{children}
|
|
||||||
</Layout.Header>
|
|
||||||
))
|
|
||||||
|
|
||||||
export default PageHeader
|
|
@ -72,7 +72,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.h-100vh {
|
.h-100vh {
|
||||||
height: calc(100vh - 64px);
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-10 {
|
.p-10 {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
@header-height: 64px;
|
@link-color: #FFFC;
|
||||||
@layout-min-height: calc(100vh - @header-height);
|
@active-bg: #c32828;
|
||||||
|
@admin-bg: #900;
|
||||||
|
@admin-active-bg: #413f3d;
|
||||||
|
|
||||||
.no-select {
|
.no-select {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@ -8,38 +10,6 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
gap: 50px;
|
|
||||||
height: @header-height;
|
|
||||||
|
|
||||||
& .logo {
|
|
||||||
padding: 8px 24px;
|
|
||||||
margin: 0 10px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .title {
|
|
||||||
flex-grow: 1;
|
|
||||||
color: #fff;
|
|
||||||
padding-left: calc(100vw / 2 - 400px);
|
|
||||||
.no-select;
|
|
||||||
}
|
|
||||||
|
|
||||||
& button {
|
|
||||||
color: rgb(255, 255, 255);
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@link-color: #FFFC;
|
|
||||||
@active-bg: #c32828;
|
|
||||||
@admin-bg: #900;
|
|
||||||
@admin-active-bg: #413f3d;
|
|
||||||
|
|
||||||
.ant-menu-submenu-popup .ant-menu-sub {
|
.ant-menu-submenu-popup .ant-menu-sub {
|
||||||
color: @link-color;
|
color: @link-color;
|
||||||
|
|
||||||
@ -88,12 +58,14 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(@header-height - 4px) !important;
|
// padding: 10px;
|
||||||
|
height: 100px !important;
|
||||||
color: @link-color;
|
color: @link-color;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: white;
|
color: white;
|
||||||
@ -115,6 +87,19 @@
|
|||||||
& .page-content {
|
& .page-content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: white;
|
background: white;
|
||||||
|
|
||||||
|
& .breadcrumb-block {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 0 15px 15px 15px;
|
||||||
|
|
||||||
|
& .ant-breadcrumb-link, .ant-breadcrumb-separator {
|
||||||
|
.no-select;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .sheet{
|
& .sheet{
|
||||||
|
Loading…
Reference in New Issue
Block a user