Optimize layout configuration to make it simpler and more flexible.

This commit is contained in:
zuiidea
2018-09-13 20:20:25 +08:00
parent 47fd515944
commit 40d3914b3c
10 changed files with 281 additions and 200 deletions
+13
View File
@@ -26,6 +26,19 @@ export default {
/chart\/Recharts\/.+Component\.js$/,
/chart\/Recharts\/Container\.js$/,
],
update: routes => {
const newRoutes = []
for (const item of routes[0].routes) {
newRoutes.push(item)
if(item.path){
newRoutes.push(
Object.assign({}, item, { path: '/:lang(en|zh)' + item.path } )
)
}
}
routes[0].routes = newRoutes
return routes
},
},
dll: {
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch', 'antd/es'],
+99
View File
@@ -0,0 +1,99 @@
import React, { PureComponent, Fragment } from 'react'
import pathToRegexp from 'path-to-regexp'
import PropTypes from 'prop-types'
import { connect } from 'dva'
import { Helmet } from 'react-helmet'
import { Loader } from 'components'
import NProgress from 'nprogress'
import config from 'utils/config'
import withRouter from 'umi/withRouter'
import PublicLayout from './PublicLayout'
import PrimaryLayout from './PrimaryLayout'
import './BaseLayout.less'
const LayoutMap = {
primary: PrimaryLayout,
public: PublicLayout,
}
/**
* Query which layout should be used for the current path based on the configuration.
* @param {layouts} layouts Layout configuration.
* @param {pathname} pathname Path name to be queried.
* @return {string} Return frist object when query success.
*/
const queryLayout = (layouts, pathname) => {
let result = 'public'
const isMatch = regepx => {
return regepx instanceof RegExp
? regepx.test(pathname)
: pathToRegexp(regepx).exec(pathname)
}
for (const item of layouts) {
let include = false
let exlude = false
if (item.include) {
for (const regepx of item.include) {
if (isMatch(regepx)) {
include = true
break
}
}
}
if (include && item.exlude) {
for (const regepx of item.exlude) {
if (isMatch(regepx)) {
exlude = true
break
}
}
}
if (include && !exlude) {
result = item.name
break
}
}
return result
}
@withRouter
@connect(({ loading }) => ({ loading }))
class BaseLayout extends PureComponent {
previousPath = ''
render() {
const { loading, children, location } = this.props
const Container = LayoutMap[queryLayout(config.layouts, location.pathname)]
const currentPath = location.pathname + location.search
if (currentPath !== this.previousPath) {
NProgress.start()
if (!loading.global) {
NProgress.done()
this.previousPath = currentPath
}
}
return (
<Fragment>
<Helmet>
<title>{config.siteName}</title>
</Helmet>
<Loader fullScreen spinning={loading.effects['app/query']} />
<Container>{children}</Container>
</Fragment>
)
}
}
BaseLayout.propTypes = {
loading: PropTypes.object,
}
export default BaseLayout
@@ -1,4 +1,5 @@
@import '~themes/vars.less';
@import '~themes/index.less';
:global {
#nprogress {
@@ -7,7 +8,7 @@
.bar {
background: @primary-color;
position: fixed;
z-index: 1024;
z-index: 2048;
top: 0;
left: 0;
right: 0;
@@ -61,6 +62,7 @@
}
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
+135
View File
@@ -0,0 +1,135 @@
/* global window */
/* global document */
import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types'
import pathToRegexp from 'path-to-regexp'
import withRouter from 'umi/withRouter'
import { connect } from 'dva'
import { MyLayout } from 'components'
import { BackTop, Layout } from 'antd'
import { classnames, config } from 'utils'
import Error from '../pages/404'
const { Content, Footer, Sider } = Layout
const { Header, Bread, styles } = MyLayout
const { prefix } = config
@withRouter
@connect(({ app, loading }) => ({ app, loading }))
class PrimaryLayout extends PureComponent {
render() {
const { children, dispatch, app, location } = this.props
const {
user,
siderFold,
darkTheme,
isNavbar,
menuPopoverVisible,
navOpenKeys,
menu,
permissions,
} = app
let { pathname } = location
pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
const current = menu.filter(item =>
pathToRegexp(item.route || '').exec(pathname)
)
const hasPermission = current.length
? permissions.visit.includes(current[0].id)
: false
const headerProps = {
menu,
user,
location,
siderFold,
isNavbar,
menuPopoverVisible,
navOpenKeys,
switchMenuPopover() {
dispatch({ type: 'app/switchMenuPopver' })
},
logout() {
dispatch({ type: 'app/logout' })
},
switchSider() {
dispatch({ type: 'app/switchSider' })
},
changeOpenKeys(openKeys) {
dispatch({
type: 'app/handleNavOpenKeys',
payload: { navOpenKeys: openKeys },
})
},
}
const siderProps = {
menu,
location,
siderFold,
darkTheme,
navOpenKeys,
changeTheme() {
dispatch({ type: 'app/switchTheme' })
},
changeOpenKeys(openKeys) {
window.localStorage.setItem(
`${prefix}navOpenKeys`,
JSON.stringify(openKeys)
)
dispatch({
type: 'app/handleNavOpenKeys',
payload: { navOpenKeys: openKeys },
})
},
}
const breadProps = {
menu,
location,
}
return (
<Fragment>
<Layout
className={classnames({
[styles.dark]: darkTheme,
[styles.light]: !darkTheme,
})}
>
{!isNavbar && (
<Sider trigger={null} collapsible collapsed={siderFold}>
{siderProps.menu.length === 0 ? null : (
<MyLayout.Sider {...siderProps} />
)}
</Sider>
)}
<Layout
style={{ height: '100vh', overflow: 'scroll' }}
id="PrimaryLayoutContainer"
>
<BackTop
target={() => document.getElementById('PrimaryLayoutContainer')}
/>
<Header {...headerProps} />
<Content>
<Bread {...breadProps} />
{hasPermission ? children : <Error />}
</Content>
<Footer>{config.footerText}</Footer>
</Layout>
</Layout>
</Fragment>
)
}
}
PrimaryLayout.propTypes = {
children: PropTypes.element.isRequired,
location: PropTypes.object,
dispatch: PropTypes.func,
app: PropTypes.object,
loading: PropTypes.object,
}
export default PrimaryLayout
+3
View File
@@ -0,0 +1,3 @@
export default ({ children }) => {
return children
}
-162
View File
@@ -1,162 +0,0 @@
/* global window */
/* global document */
import React from 'react'
import NProgress from 'nprogress'
import PropTypes from 'prop-types'
import pathToRegexp from 'path-to-regexp'
import { connect } from 'dva'
import { Loader, MyLayout } from 'components'
import { BackTop, Layout } from 'antd'
import { classnames, config } from 'utils'
import { Helmet } from 'react-helmet'
import { withRouter } from 'dva/router'
import Error from '../pages/404'
import '../themes/index.less'
import './app.less'
const { Content, Footer, Sider } = Layout
const { Header, Bread, styles } = MyLayout
const { prefix, openPages } = config
let lastHref
const App = ({ children, dispatch, app, loading, location }) => {
const {
user,
siderFold,
darkTheme,
isNavbar,
menuPopoverVisible,
navOpenKeys,
menu,
permissions,
} = app
let { pathname } = location
pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
const { logo } = config
const current = menu.filter(item =>
pathToRegexp(item.route || '').exec(pathname)
)
const hasPermission = current.length
? permissions.visit.includes(current[0].id)
: false
const { href } = window.location
if (lastHref !== href) {
NProgress.start()
if (!loading.global) {
NProgress.done()
lastHref = href
}
}
const headerProps = {
menu,
user,
location,
siderFold,
isNavbar,
menuPopoverVisible,
navOpenKeys,
switchMenuPopover() {
dispatch({ type: 'app/switchMenuPopver' })
},
logout() {
dispatch({ type: 'app/logout' })
},
switchSider() {
dispatch({ type: 'app/switchSider' })
},
changeOpenKeys(openKeys) {
dispatch({
type: 'app/handleNavOpenKeys',
payload: { navOpenKeys: openKeys },
})
},
}
const siderProps = {
menu,
location,
siderFold,
darkTheme,
navOpenKeys,
changeTheme() {
dispatch({ type: 'app/switchTheme' })
},
changeOpenKeys(openKeys) {
window.localStorage.setItem(
`${prefix}navOpenKeys`,
JSON.stringify(openKeys)
)
dispatch({
type: 'app/handleNavOpenKeys',
payload: { navOpenKeys: openKeys },
})
},
}
const breadProps = {
menu,
location,
}
if (openPages && openPages.includes(pathname)) {
return (
<div>
<Loader fullScreen spinning={loading.effects['app/query']} />
{children}
</div>
)
}
return (
<div>
<Loader fullScreen spinning={loading.effects['app/query']} />
<Helmet>
<title>ANTD ADMIN</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href={logo} type="image/x-icon" />
</Helmet>
<Layout
className={classnames({
[styles.dark]: darkTheme,
[styles.light]: !darkTheme,
})}
>
{!isNavbar && (
<Sider trigger={null} collapsible collapsed={siderFold}>
{siderProps.menu.length === 0 ? null : (
<MyLayout.Sider {...siderProps} />
)}
</Sider>
)}
<Layout
style={{ height: '100vh', overflow: 'scroll' }}
id="mainContainer"
>
<BackTop target={() => document.getElementById('mainContainer')} />
<Header {...headerProps} />
<Content>
<Bread {...breadProps} />
{hasPermission ? children : <Error />}
</Content>
<Footer>{config.footerText}</Footer>
</Layout>
</Layout>
</div>
)
}
App.propTypes = {
children: PropTypes.element.isRequired,
location: PropTypes.object,
dispatch: PropTypes.func,
app: PropTypes.object,
loading: PropTypes.object,
}
export default withRouter(
connect(({ app, loading }) => ({ app, loading }))(App)
)
+13 -10
View File
@@ -1,13 +1,16 @@
import React from 'react'
import React, { PureComponent } from 'react'
import { LocaleProvider } from 'antd'
import enUS from 'antd/lib/locale-provider/en_US'
import withRouter from 'umi/withRouter'
import App from './app'
import BaseLayout from './BaseLayout'
export default withRouter(props => {
return (
<LocaleProvider locale={enUS}>
<App>{props.children}</App>
</LocaleProvider>
)
})
class Layout extends PureComponent {
render() {
return (
<LocaleProvider locale={enUS}>
<BaseLayout>{this.props.children}</BaseLayout>
</LocaleProvider>
)
}
}
export default Layout
+8 -8
View File
@@ -103,14 +103,14 @@ export default {
config.openPages &&
config.openPages.indexOf(locationPathname) < 0
) {
yield put(
routerRedux.push({
pathname: '/login',
search: stringify({
from: locationPathname,
}),
})
)
// yield put(
// routerRedux.push({
// pathname: '/login',
// search: stringify({
// from: locationPathname,
// }),
// })
// )
}
},
-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="A admin dashboard application demo built upon Ant Design and Dva.js">
<title>antd admin</title>
<!--[if lte IE 10]>
<script
src="https://as.alipayobjects.com/g/component/??console-polyfill/0.2.2/index.js,media-match/2.0.2/media.match.min.js"></script>
<![endif]-->
</head>
<body>
<div id="root"></div>
</body>
</html>
+7
View File
@@ -5,4 +5,11 @@ module.exports = {
logoPath: '/logo.svg',
apiPrefix: '/api/v1',
openPages: ['/login'],
layouts: [
{
name: 'primary',
include: [/.*/],
exlude: [/(\/(en|zh))*\/login/],
},
],
}