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 ( {this.getNavMenuItems(this.menus)} 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 = ( @@ -254,7 +262,7 @@ export default class Analysis extends Component { } - total={() => } + total={() => 126560} footer={} contentHeight={46} > @@ -453,15 +461,9 @@ export default class Analysis extends Component { ( - now.y + pre, 0)), - }} - /> - )} + total={() => {salesPieData.reduce((pre, now) => now.y + pre, 0)}} data={salesPieData} - valueFormat={val => } + valueFormat={value => {value}} height={248} lineWidth={4} /> diff --git a/src/routes/Forms/StepForm/Step3.js b/src/routes/Forms/StepForm/Step3.js index 08fc5d90f99f4ca9157430c06df76b9d069fb459..9a8d6d46cc92a7aa57881a1c9f83511c35b24560 100644 --- a/src/routes/Forms/StepForm/Step3.js +++ b/src/routes/Forms/StepForm/Step3.js @@ -14,28 +14,34 @@ class Step3 extends React.PureComponent { const information = (
- + 付款账户: - {data.payAccount} + + {data.payAccount} + - + 收款账户: - {data.receiverAccount} + + {data.receiverAccount} + - + 收款人姓名: - {data.receiverName} + + {data.receiverName} + - + 转账金额: - + {data.amount} diff --git a/src/routes/Forms/StepForm/index.js b/src/routes/Forms/StepForm/index.js index 23c660d931e8af41c3f6e37db780b29afa4d46ce..273bfad1a05848b32dbe2c696e7701e31ffc9f92 100644 --- a/src/routes/Forms/StepForm/index.js +++ b/src/routes/Forms/StepForm/index.js @@ -24,10 +24,11 @@ export default class StepForm extends PureComponent { } } render() { - const { match, routerData } = this.props; + const { match, routerData, location } = this.props; return ( diff --git a/src/routes/Forms/StepForm/style.less b/src/routes/Forms/StepForm/style.less index 6dbbd629c2efc97bd4570d528d3b0e4285948446..713dd54c5ba3fbc58f50f77cffbedd2b30a361c9 100644 --- a/src/routes/Forms/StepForm/style.less +++ b/src/routes/Forms/StepForm/style.less @@ -60,6 +60,9 @@ color: @heading-color; text-align: right; padding-right: 8px; + @media screen and (max-width: @screen-sm) { + text-align: left; + } } } diff --git a/src/routes/Result/Success.js b/src/routes/Result/Success.js index c27567a8f5801f6f4f0a50983b9d42a3f2ba73c7..9e20e2780a4117bd4a3cd018aa11cba340ebc499 100644 --- a/src/routes/Result/Success.js +++ b/src/routes/Result/Success.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react'; import { Button, Row, Col, Icon, Steps, Card } from 'antd'; -import Result from '../../components/Result'; +import Result from 'components/Result'; import PageHeaderLayout from '../../layouts/PageHeaderLayout'; const { Step } = Steps; diff --git a/src/routes/User/Login.less b/src/routes/User/Login.less index c34ab3d824ccefcede1546afda41f82ee1a14bdb..14a99ff11664d32aec1411154ec735ead61aae04 100644 --- a/src/routes/User/Login.less +++ b/src/routes/User/Login.less @@ -3,6 +3,9 @@ .main { width: 368px; margin: 0 auto; + @media screen and (max-width: @screen-sm) { + width: 95%; + } .icon { font-size: 24px; diff --git a/tests/run-tests.js b/tests/run-tests.js index d48a759f7a128e1cf6e1fe32d1f9816f5116b7da..7a51228c4138805c5ed43dd198cb6aeb4bd314a7 100644 --- a/tests/run-tests.js +++ b/tests/run-tests.js @@ -30,8 +30,9 @@ startServer.stdout.on('data', data => { const testCmd = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['test'], { stdio: 'inherit', }); - testCmd.on('exit', () => { + testCmd.on('exit', code => { startServer.kill(); + process.exit(code); }); } });