Commit c82c4532 authored by 陈帅's avatar 陈帅

Merge branch 'master' into v4

parents ebd13177 7679507b
......@@ -52,6 +52,10 @@ jobs:
displayName: install
- script: npm run lint
displayName: lint
- script: npm run test:all
env:
PROGRESS: none
displayName: test
- script: npm run build
env:
PROGRESS: none
......
// ps https://github.com/GoogleChrome/puppeteer/issues/3120
module.exports = {
launch: {
args: ['--disable-gpu', '--disable-dev-shm-usage', '--no-first-run', '--no-zygote'],
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--no-first-run',
'--no-zygote',
'--no-sandbox',
],
},
};
......@@ -5,7 +5,7 @@
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {
"analyze": "cross-env ANALYZE=1 umi build",
"build": "umi build",
"build": "umi build && npm run functions:build",
"dev": "cross-env APP_TYPE=site umi dev",
"dev:no-mock": "cross-env MOCK=none umi dev",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
......@@ -73,9 +73,13 @@
"react-container-query": "^0.11.0",
"react-copy-to-clipboard": "^5.0.1",
"react-document-title": "^2.0.3",
"react-dom": "^16.7.0",
"react-fittext": "^1.0.0",
"react-media": "^1.9.2",
"react-media-hook2": "^1.0.2",
"umi": "^2.6.10",
"umi-request": "^1.0.0"
"umi-plugin-react": "^1.7.2",
"umi-request": "^1.0.5"
},
"devDependencies": {
"@types/classnames": "^2.2.7",
......@@ -110,6 +114,7 @@
"mockjs": "^1.0.1-beta3",
"netlify-lambda": "^1.4.3",
"prettier": "^1.16.4",
"serverless-http": "^1.9.1",
"slash2": "^2.0.0",
"stylelint": "^9.10.1",
"stylelint-config-css-modules": "^1.3.0",
......
import React from 'react';
import { connect } from 'dva';
import styles from './GridContent.less';
const GridContent = props => {
const { contentWidth, children } = props;
let className = `${styles.main}`;
if (contentWidth === 'Fixed') {
className = `${styles.main} ${styles.wide}`;
}
return <div className={className}>{children}</div>;
};
export default connect(({ setting }) => ({
contentWidth: setting.contentWidth,
}))(GridContent);
.main {
width: 100%;
height: 100%;
min-height: 100%;
transition: 0.3s;
&.wide {
max-width: 1200px;
margin: 0 auto;
}
}
import React from 'react';
import pathToRegexp from 'path-to-regexp';
import Link from 'umi/link';
import { FormattedMessage } from 'umi-plugin-react/locale';
import { urlToList } from '../_utils/pathTools';
// 渲染Breadcrumb 子节点
// Render the Breadcrumb child node
const itemRender = (route, params, routes, paths) => {
const last = routes.indexOf(route) === routes.length - 1;
return last || !route.component ? (
<span>{route.breadcrumbName}</span>
) : (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
);
};
const renderItemLocal = item => {
if (item.locale) {
return <FormattedMessage id={item.locale} defaultMessage={item.name} />;
}
return item.name;
};
export const getBreadcrumb = (breadcrumbNameMap, url) => {
let breadcrumb = breadcrumbNameMap[url];
if (!breadcrumb) {
Object.keys(breadcrumbNameMap).forEach(item => {
if (pathToRegexp(item).test(url)) {
breadcrumb = breadcrumbNameMap[item];
}
});
}
return breadcrumb || {};
};
export const getBreadcrumbProps = props => {
const { routes, params, location, breadcrumbNameMap } = props;
return {
routes,
params,
routerLocation: location,
breadcrumbNameMap,
};
};
// Generated according to props
const conversionFromProps = props => {
const { breadcrumbList } = props;
return breadcrumbList.map(item => {
const { title, href } = item;
return {
path: href,
breadcrumbName: title,
};
});
};
const conversionFromLocation = (routerLocation, breadcrumbNameMap, props) => {
const { home } = props;
// Convert the url to an array
const pathSnippets = urlToList(routerLocation.pathname);
// Loop data mosaic routing
const extraBreadcrumbItems = pathSnippets.map(url => {
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
if (currentBreadcrumb.inherited) {
return null;
}
const name = renderItemLocal(currentBreadcrumb);
const { hideInBreadcrumb } = currentBreadcrumb;
return name && !hideInBreadcrumb
? {
path: url,
breadcrumbName: name,
}
: null;
});
// Add home breadcrumbs to your head if defined
if (home) {
extraBreadcrumbItems.unshift({
path: '/',
breadcrumbName: home,
});
}
return extraBreadcrumbItems;
};
/**
* 将参数转化为面包屑
* Convert parameters into breadcrumbs
*/
export const conversionBreadcrumbList = props => {
const { breadcrumbList } = props;
const { routes, params, routerLocation, breadcrumbNameMap } = getBreadcrumbProps(props);
if (breadcrumbList && breadcrumbList.length) {
return conversionFromProps();
}
// 如果传入 routes 和 params 属性
// If pass routes and params attributes
if (routes && params) {
return {
routes: routes.filter(route => route.breadcrumbName),
params,
itemRender,
};
}
// 根据 location 生成 面包屑
// Generate breadcrumbs based on location
if (routerLocation && routerLocation.pathname) {
return {
routes: conversionFromLocation(routerLocation, breadcrumbNameMap, props),
itemRender,
};
}
return {};
};
import React from 'react';
import { FormattedMessage } from 'umi-plugin-react/locale';
import Link from 'umi/link';
import { PageHeader, Tabs, Typography } from 'antd';
import { connect } from 'dva';
import classNames from 'classnames';
import GridContent from './GridContent';
import styles from './index.less';
import MenuContext from '@/layouts/MenuContext';
import { conversionBreadcrumbList } from './breadcrumb';
const { Title } = Typography;
/**
* render Footer tabList
* In order to be compatible with the old version of the PageHeader
* basically all the functions are implemented.
*/
const renderFooter = ({ tabList, activeKeyProps, onTabChange, tabBarExtraContent }) => {
return tabList && tabList.length ? (
<Tabs
className={styles.tabs}
{...activeKeyProps}
onChange={key => {
if (onTabChange) {
onTabChange(key);
}
}}
tabBarExtraContent={tabBarExtraContent}
>
{tabList.map(item => (
<Tabs.TabPane tab={item.tab} key={item.key} />
))}
</Tabs>
) : null;
};
const PageHeaderWrapper = ({
children,
contentWidth,
wrapperClassName,
top,
title,
content,
logo,
extraContent,
...restProps
}) => {
return (
<div style={{ margin: '-24px -24px 0' }} className={classNames(classNames, styles.main)}>
{top}
{title && content && (
<MenuContext.Consumer>
{value => {
return (
<PageHeader
wide={contentWidth === 'Fixed'}
title={
<Title
level={4}
style={{
marginBottom: 0,
}}
>
{title}
</Title>
}
key="pageheader"
{...restProps}
breadcrumb={conversionBreadcrumbList({
...value,
...restProps,
home: <FormattedMessage id="menu.home" defaultMessage="Home" />,
})}
className={styles.pageHeader}
linkElement={Link}
footer={renderFooter(restProps)}
>
<div className={styles.detail}>
{logo && <div className={styles.logo}>{logo}</div>}
<div className={styles.main}>
<div className={styles.row}>
{content && <div className={styles.content}>{content}</div>}
{extraContent && <div className={styles.extraContent}>{extraContent}</div>}
</div>
</div>
</div>
</PageHeader>
);
}}
</MenuContext.Consumer>
)}
{children ? (
<div className={styles['children-content']}>
<GridContent>{children}</GridContent>
</div>
) : null}
</div>
);
};
export default connect(({ setting }) => ({
contentWidth: setting.contentWidth,
}))(PageHeaderWrapper);
@import '~antd/lib/style/themes/default.less';
.children-content {
margin: 24px 24px 0;
}
.main {
:global {
.ant-page-header {
padding: 16px 32px 0;
background: #fff;
border-bottom: 1px solid #e8e8e8;
}
}
.wide {
max-width: 1200px;
margin: auto;
}
.detail {
display: flex;
}
.row {
display: flex;
width: 100%;
}
.logo {
flex: 0 1 auto;
margin-right: 16px;
padding-top: 1px;
> img {
display: block;
width: 28px;
height: 28px;
border-radius: @border-radius-base;
}
}
.title-content {
margin-bottom: 16px;
}
@media screen and (max-width: @screen-sm) {
.content {
margin: 24px 0 0;
}
}
.title,
.content {
flex: auto;
}
.extraContent,
.main {
flex: 0 1 auto;
}
.main {
width: 100%;
}
.title {
margin-bottom: 16px;
}
.logo,
.content,
.extraContent {
margin-bottom: 16px;
}
.extraContent {
min-width: 242px;
margin-left: 88px;
text-align: right;
}
}
@media screen and (max-width: @screen-xl) {
.extraContent {
margin-left: 44px;
}
}
@media screen and (max-width: @screen-lg) {
.extraContent {
margin-left: 20px;
}
}
@media screen and (max-width: @screen-md) {
.row {
display: block;
}
.action,
.extraContent {
margin-left: 0;
text-align: left;
}
}
@media screen and (max-width: @screen-sm) {
.detail {
display: block;
}
}
const RouterConfig = [];
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
function formatter(data) {
return data
.reduce((pre, item) => {
if (item.routes) {
pre.push(item.routes[0].path);
} else {
pre.push(item.path);
}
return pre;
}, [])
.filter(item => item);
}
describe('Homepage', () => {
const testPage = path => async () => {
await page.goto(`${BASE_URL}${path}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
};
beforeAll(async () => {
jest.setTimeout(1000000);
await page.setCacheEnabled(false);
});
const routers = formatter(RouterConfig[1].routes);
routers.forEach(route => {
it(`test pages ${route}`, testPage(route));
});
});
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
describe('Homepage', () => {
beforeAll(async () => {
jest.setTimeout(1000000);
});
it('topmenu should have footer', async () => {
const params = '/form/basic-form?navTheme=light&layout=topmenu';
await page.goto(`${BASE_URL}${params}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
});
});
......@@ -136,7 +136,10 @@ class HeaderView extends Component<HeaderViewProps, HeaderViewState> {
const isTop = layout === 'topmenu';
const width = this.getHeadWidth();
const HeaderDom = visible ? (
<Header style={{ padding: 0, width }} className={fixedHeader ? styles.fixedHeader : ''}>
<Header
style={{ padding: 0, width, zIndex: 2 }}
className={fixedHeader ? styles.fixedHeader : ''}
>
{isTop && !isMobile ? (
<TopNavHeader
theme={navTheme}
......
// use localStorage to store the authority info, which might be sent from server in actual project.
const { NODE_ENV } = process.env;
export function getAuthority(str?: string): any {
// return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
const authorityString =
......@@ -13,6 +15,9 @@ export function getAuthority(str?: string): any {
if (typeof authority === 'string') {
return [authority];
}
if (!authority && NODE_ENV !== 'production') {
return ['admin'];
}
return authority;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment