diff --git a/.babelrc.js b/.babelrc.js
index e89d220448ee10197b332c1b9a9849b3942815f7..20a0892bb2d10d31f820b3f6e4b164a791aedc69 100644
--- a/.babelrc.js
+++ b/.babelrc.js
@@ -3,7 +3,7 @@ const path = require('path');
module.exports = {
plugins: [
[
- 'babel-plugin-module-resolver',
+ 'module-resolver',
{
alias: {
components: path.join(__dirname, './src/components'),
diff --git a/.gitignore b/.gitignore
index fd9bf875668fb3b3a8d6df470ce665a4ad130bd1..4770ad5a6684369b81af366b69933de3fc2d64a6 100755
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ yarn.lock
package-lock.json
*bak
jsconfig.json
+.vscode/settings.json
diff --git a/.prettierignore b/.prettierignore
index 10e1da6a909c9d536ff07086520067175197b5df..95765e948981b843042c3767b21c2e31d76bc66b 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,5 @@
**/*.md
**/*.svg
**/*.ejs
+**/*.html
package.json
diff --git a/package.json b/package.json
index c1c2c013975502c67213f257335d817ffafb5d48..8cd618ff0bba0f5fddc13599fb856c684a25246f 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ant-design-pro",
- "version": "1.3.0",
+ "version": "2.0.0-beta.1",
"description": "An out-of-box UI solution for enterprise applications",
"private": true,
"scripts": {
@@ -78,18 +78,18 @@
"pro-download": "^1.0.1",
"redbox-react": "^1.5.0",
"regenerator-runtime": "^0.11.1",
- "roadhog": "^2.3.0",
- "roadhog-api-doc": "^1.0.2",
- "stylelint": "^9.2.0",
+ "roadhog": "^2.4.1",
+ "roadhog-api-doc": "^1.0.3",
+ "stylelint": "^8.4.0",
"stylelint-config-prettier": "^3.0.4",
"stylelint-config-standard": "^18.0.0"
},
"optionalDependencies": {
- "puppeteer": "^1.3.0"
+ "puppeteer": "^1.4.0"
},
"lint-staged": {
"**/*.{js,jsx,less}": [
- "prettier --wirter",
+ "prettier --write",
"git add"
],
"**/*.{js,jsx}": "npm run lint-staged:js",
diff --git a/src/components/Charts/index.js b/src/components/Charts/index.js
index cacedbce496cd2b4c1ddc986c68a817b9bdc094c..78863fabdb7615138baf2063f1c4ed13285abade 100644
--- a/src/components/Charts/index.js
+++ b/src/components/Charts/index.js
@@ -13,7 +13,7 @@ import WaterWave from './WaterWave';
import TagCloud from './TagCloud';
import TimelineChart from './TimelineChart';
-const yuan = val => `¥ ${numeral(val).format('0,0')}`;
+const yuan = val => `¥ ${numeral(val).format('0,0')}`;
const Charts = {
yuan,
diff --git a/src/components/DescriptionList/DescriptionList.js b/src/components/DescriptionList/DescriptionList.js
index 73ba989c0e06b4e4ef91c906a79b7f1f48e9ef10..73bb5f5f92262c1e1f550d0e6e2a28811717b5da 100644
--- a/src/components/DescriptionList/DescriptionList.js
+++ b/src/components/DescriptionList/DescriptionList.js
@@ -22,7 +22,10 @@ const DescriptionList = ({
{title ?
{title}
: null}
- {React.Children.map(children, child => React.cloneElement(child, { column }))}
+ {React.Children.map(
+ children,
+ child => (child ? React.cloneElement(child, { column }) : child)
+ )}
);
diff --git a/src/components/Login/index.d.ts b/src/components/Login/index.d.ts
index 2c0fd01a4c31e2a08860353f2eac7cae5d8b3ae0..cd88a8b693d7f219221decc278329ffcbe4db60e 100644
--- a/src/components/Login/index.d.ts
+++ b/src/components/Login/index.d.ts
@@ -18,6 +18,7 @@ export interface LoginItemProps {
rules?: any[];
style?: React.CSSProperties;
onGetCaptcha?: () => void;
+ placeholder?: string;
}
export class LoginItem extends React.Component {}
diff --git a/src/components/Login/index.less b/src/components/Login/index.less
index e0a984cd2aa20043d5599f4a98ea65afd31905ab..2894749b79a37bf658bb3f13d191613771204544 100644
--- a/src/components/Login/index.less
+++ b/src/components/Login/index.less
@@ -35,6 +35,7 @@
.getCaptcha {
display: block;
width: 100%;
+ height: 42px;
}
.submit {
diff --git a/src/components/PageHeader/index.js b/src/components/PageHeader/index.js
index 0d3f63f27703bec880a1e32b2d4422392efdc8dc..81ef6c1c69dc56adeb318ac5bc4299f130f90a2b 100644
--- a/src/components/PageHeader/index.js
+++ b/src/components/PageHeader/index.js
@@ -25,10 +25,12 @@ export default class PageHeader extends PureComponent {
componentDidMount() {
this.getBreadcrumbDom();
}
- componentWillReceiveProps() {
- this.getBreadcrumbDom();
- }
+ componentDidUpdate(preProps) {
+ if (preProps.tabActiveKey !== this.props.tabActiveKey) {
+ this.getBreadcrumbDom();
+ }
+ }
onChange = key => {
if (this.props.onTabChange) {
this.props.onTabChange(key);
diff --git a/src/components/Result/index.less b/src/components/Result/index.less
index 9953392c0166c0ac641215c27c9664c5fb44aa3d..5cd2aff589606f5684fd1c8aeff6540ee81b8b6c 100644
--- a/src/components/Result/index.less
+++ b/src/components/Result/index.less
@@ -4,6 +4,9 @@
text-align: center;
width: 72%;
margin: 0 auto;
+ @media screen and (max-width: @screen-xs) {
+ width: 100%;
+ }
.icon {
font-size: 72px;
@@ -39,6 +42,10 @@
padding: 24px 40px;
border-radius: @border-radius-sm;
text-align: left;
+
+ @media screen and (max-width: @screen-xs) {
+ padding: 18px 20px;
+ }
}
.actions {
diff --git a/src/components/Sidebar/index.js b/src/components/Sidebar/index.js
index 3635912fe034ef936c080d2885d77058530211d3..ef448460078b10d70a24ca11859fd8de18f24058 100644
--- a/src/components/Sidebar/index.js
+++ b/src/components/Sidebar/index.js
@@ -87,7 +87,7 @@ class Sidebar extends PureComponent {
this.changeSetting('fixSiderbar', checked);
};
changeSetting = (key, value) => {
- const nextState = {};
+ const nextState = { ...this.props.setting };
nextState[key] = value;
if (key === 'layout') {
if (value === 'topmenu') {
diff --git a/src/components/SiderMenu/BaseMenu.js b/src/components/SiderMenu/BaseMenu.js
index 7c074d383205071ad67cef983e5911c15a79b6ea..cbbdd386fc89d661022b36557539230b718928c9 100644
--- a/src/components/SiderMenu/BaseMenu.js
+++ b/src/components/SiderMenu/BaseMenu.js
@@ -67,7 +67,9 @@ export default class BaseMenu extends PureComponent {
};
// Get the currently selected menu
getSelectedMenuKeys = () => {
- const { location: { pathname } } = this.props;
+ const {
+ location: { pathname },
+ } = this.props;
return urlToList(pathname).map(itemPath => getMenuMatches(this.flatMenuKeys, itemPath).pop());
};
/**
@@ -148,20 +150,27 @@ export default class BaseMenu extends PureComponent {
}
};
render() {
- const { openKeys } = this.props;
+ const { openKeys, theme, mode } = this.props;
// if pathname can't match, use the nearest parent's key
let selectedKeys = this.getSelectedMenuKeys();
if (!selectedKeys.length && openKeys) {
selectedKeys = [openKeys[openKeys.length - 1]];
}
+ let props = {};
+ if (openKeys) {
+ props = {
+ openKeys,
+ };
+ }
return (
diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js
index 176b5f66262ecf33c11915499c1c9035ce1381da..cb308d405ebadcb55b40b9136783ee51d915f53c 100644
--- a/src/components/SiderMenu/SiderMenu.js
+++ b/src/components/SiderMenu/SiderMenu.js
@@ -1,5 +1,6 @@
import React, { PureComponent } from 'react';
import { Layout, Menu, Icon } from 'antd';
+import pathToRegexp from 'path-to-regexp';
import { Link } from 'dva/router';
import styles from './index.less';
import BaseMenu, { getMenuMatches } from './BaseMenu';
@@ -37,6 +38,32 @@ const getIcon = icon => {
return icon;
};
+/**
+ * Recursively flatten the data
+ * [{path:string},{path:string}] => {path,path2}
+ * @param menu
+ */
+export const getFlatMenuKeys = menu =>
+ menu.reduce((keys, item) => {
+ keys.push(item.path);
+ if (item.children) {
+ return keys.concat(getFlatMenuKeys(item.children));
+ }
+ return keys;
+ }, []);
+
+/**
+ * Find all matched menu keys based on paths
+ * @param flatMenuKeys: [/abc, /abc/:id, /abc/:id/info]
+ * @param paths: [/abc, /abc/11, /abc/11/info]
+ */
+export const getMenuMatchKeys = (flatMenuKeys, paths) =>
+ paths.reduce(
+ (matchKeys, path) =>
+ matchKeys.concat(flatMenuKeys.filter(item => pathToRegexp(item).test(path))),
+ []
+ );
+
export default class SiderMenu extends PureComponent {
static getDerivedStateFromProps(nextProps) {
return {
@@ -45,6 +72,8 @@ export default class SiderMenu extends PureComponent {
}
constructor(props) {
super(props);
+ this.menus = props.menuData;
+ this.flatMenuKeys = getFlatMenuKeys(props.menuData);
this.state = {
openKeys: getDefaultCollapsedSubMenus(props),
};
@@ -54,6 +83,15 @@ export default class SiderMenu extends PureComponent {
* Convert pathname to openKeys
* /list/search/articles = > ['list','/list/search']
* @param props
+ */
+ getDefaultCollapsedSubMenus(props) {
+ const {
+ location: { pathname },
+ } =
+ props || this.props;
+ return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname));
+ }
+ /**
* 判断是否是http链接.返回 Link 或 a
* Judge whether it is http link.return a or Link
* @memberof SiderMenu
@@ -120,6 +158,47 @@ export default class SiderMenu extends PureComponent {
return {this.getMenuItemPath(item)};
}
};
+ /**
+ * 获得菜单子节点
+ * @memberof SiderMenu
+ */
+ getNavMenuItems = menusData => {
+ if (!menusData) {
+ return [];
+ }
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map(item => {
+ // make dom
+ const ItemDom = this.getSubMenuOrItem(item);
+ return this.checkPermissionItem(item.authority, ItemDom);
+ })
+ .filter(item => item);
+ };
+ // Get the currently selected menu
+ getSelectedMenuKeys = () => {
+ const {
+ location: { pathname },
+ } = this.props;
+ return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname));
+ };
+ // conversion Path
+ // 转化路径
+ conversionPath = path => {
+ if (path && path.indexOf('http') === 0) {
+ return path;
+ } else {
+ return `/${path || ''}`.replace(/\/+/g, '/');
+ }
+ };
+ // permission to check
+ checkPermissionItem = (authority, ItemDom) => {
+ if (this.props.Authorized && this.props.Authorized.check) {
+ const { check } = this.props.Authorized;
+ return check(authority, ItemDom);
+ }
+ return ItemDom;
+ };
isMainMenu = key => {
return this.props.menuData.some(item => key && (item.key === key || item.path === key));
};
diff --git a/src/components/SiderMenu/SilderMenu.test.js b/src/components/SiderMenu/SilderMenu.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f01f6695e45f4a8e6a808434666880944c00019
--- /dev/null
+++ b/src/components/SiderMenu/SilderMenu.test.js
@@ -0,0 +1,72 @@
+import { urlToList } from '../_utils/pathTools';
+import { getFlatMenuKeys, getMenuMatchKeys } from './SiderMenu';
+
+const menu = [
+ {
+ path: '/dashboard',
+ children: [
+ {
+ path: '/dashboard/name',
+ },
+ ],
+ },
+ {
+ path: '/userinfo',
+ children: [
+ {
+ path: '/userinfo/:id',
+ children: [
+ {
+ path: '/userinfo/:id/info',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const flatMenuKeys = getFlatMenuKeys(menu);
+
+describe('test convert nested menu to flat menu', () => {
+ it('simple menu', () => {
+ expect(flatMenuKeys).toEqual([
+ '/dashboard',
+ '/dashboard/name',
+ '/userinfo',
+ '/userinfo/:id',
+ '/userinfo/:id/info',
+ ]);
+ });
+});
+
+describe('test menu match', () => {
+ it('simple path', () => {
+ expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard'))).toEqual(['/dashboard']);
+ });
+
+ it('error path', () => {
+ expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboardname'))).toEqual([]);
+ });
+
+ it('Secondary path', () => {
+ expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard/name'))).toEqual([
+ '/dashboard',
+ '/dashboard/name',
+ ]);
+ });
+
+ it('Parameter path', () => {
+ expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144'))).toEqual([
+ '/userinfo',
+ '/userinfo/:id',
+ ]);
+ });
+
+ it('three parameter path', () => {
+ expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144/info'))).toEqual([
+ '/userinfo',
+ '/userinfo/:id',
+ '/userinfo/:id/info',
+ ]);
+ });
+});
diff --git a/src/e2e/home.e2e.js b/src/e2e/home.e2e.js
index 05377e852424f2835a77d3e15fd8f4604463b3fb..50447a4a12b5efcccdc2dbf55190a041af313245 100644
--- a/src/e2e/home.e2e.js
+++ b/src/e2e/home.e2e.js
@@ -2,7 +2,7 @@ import puppeteer from 'puppeteer';
describe('Homepage', () => {
it('it should have logo text', async () => {
- const browser = await puppeteer.launch();
+ const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
await page.goto('http://localhost:8000', { waitUntil: 'networkidle2' });
await page.waitForSelector('h1');
diff --git a/src/e2e/login.e2e.js b/src/e2e/login.e2e.js
index 6095b9e5be9520550c2a01e72de9713f36fd8c7e..a102a351160a21eb4ff6805ebf4b821a0dc60ac7 100644
--- a/src/e2e/login.e2e.js
+++ b/src/e2e/login.e2e.js
@@ -5,7 +5,7 @@ describe('Login', () => {
let page;
beforeAll(async () => {
- browser = await puppeteer.launch();
+ browser = await puppeteer.launch({ args: ['--no-sandbox'] });
});
beforeEach(async () => {
diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js
index 21d2a420c827e91e05d883a83a556c51be16a05b..d4853390c28fe97adb35ef14df2ab9297d1e9e34 100644
--- a/src/layouts/BasicLayout.js
+++ b/src/layouts/BasicLayout.js
@@ -5,6 +5,7 @@ import { connect } from 'dva';
import { Route, Redirect, Switch } from 'dva/router';
import { ContainerQuery } from 'react-container-query';
import classNames from 'classnames';
+import pathToRegexp from 'path-to-regexp';
import SiderMenu from '../components/SiderMenu';
import NotFound from '../routes/Exception/404';
import { getRoutes } from '../utils/utils';
@@ -70,8 +71,15 @@ class BasicLayout extends React.PureComponent {
const { routerData, location } = this.props;
const { pathname } = location;
let title = 'Ant Design Pro';
- if (routerData[pathname] && routerData[pathname].name) {
- title = `${routerData[pathname].name} - Ant Design Pro`;
+ let currRouterData = null;
+ // match params path
+ Object.keys(routerData).forEach(key => {
+ if (pathToRegexp(key).test(pathname)) {
+ currRouterData = routerData[key];
+ }
+ });
+ if (currRouterData && currRouterData.name) {
+ title = `${currRouterData.name} - Ant Design Pro`;
}
return title;
}
diff --git a/src/layouts/LoadingPage.js b/src/layouts/LoadingPage.js
index f5a110b8cc69dc43db90b7358efa8f5f40c726ef..ee7029b30f0f24709a10b3605abc41a603f191e9 100644
--- a/src/layouts/LoadingPage.js
+++ b/src/layouts/LoadingPage.js
@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
import { Spin } from 'antd';
import { connect } from 'dva';
import { enquireScreen, unenquireScreen } from 'enquire-js';
-
import BasicLayout from './BasicLayout';
import { getMenuData } from '../common/menu';
/**
@@ -35,7 +34,6 @@ class LoadingPage extends PureComponent {
loading: true,
isMobile: false,
};
-
componentDidMount() {
this.enquireHandler = enquireScreen(mobile => {
this.setState({
@@ -45,8 +43,8 @@ class LoadingPage extends PureComponent {
this.props.dispatch({
type: 'user/fetchCurrent',
});
- this.initSetting();
this.hideLoading();
+ this.initSetting();
}
componentWillUnmount() {
unenquireScreen(this.enquireHandler);
diff --git a/src/models/setting.js b/src/models/setting.js
index fe9fdb0316815f166d492964ff3e44d375177e0f..cf5a8380baa1830352bea56ec48a94777b6ce3f1 100644
--- a/src/models/setting.js
+++ b/src/models/setting.js
@@ -11,19 +11,21 @@ const defaultSetting = {
};
export default {
namespace: 'setting',
-
state: defaultSetting,
reducers: {
getSetting(state) {
- const setting = { ...state };
+ const setting = {};
const urlParams = new URL(window.location.href);
Object.keys(state).forEach(key => {
if (urlParams.searchParams.has(key)) {
const value = urlParams.searchParams.get(key);
- setting[key] = value === '1' ? true : '1';
+ setting[key] = value === '1' ? true : value;
}
});
- return setting;
+ return {
+ ...state,
+ ...setting,
+ };
},
changeSetting(state, { payload }) {
const urlParams = new URL(window.location.href);
diff --git a/src/routes/Dashboard/Analysis.js b/src/routes/Dashboard/Analysis.js
index 65354a4aa2c3ac45e22b5e2ef73739205081735d..d04c4570503095c4fe160494b609e496a0560bc3 100644
--- a/src/routes/Dashboard/Analysis.js
+++ b/src/routes/Dashboard/Analysis.js
@@ -44,6 +44,12 @@ for (let i = 0; i < 7; i += 1) {
});
}
+const Yuan = ({ children }) => (
+ /* eslint-disable-line react/no-danger */
+);
+
@connect(({ chart, loading }) => ({
chart,
loading: loading.effects['chart/fetch'],
@@ -132,7 +138,9 @@ export default class Analysis extends Component {
const salesPieData =
salesType === 'all'
? salesTypeData
- : salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
+ : salesType === 'online'
+ ? salesTypeDataOnline
+ : salesTypeDataOffline;
const menu = (