From 27562512414337ad368a622fe7ca22eaf248cf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B8=85?= Date: Fri, 23 Nov 2018 17:09:40 +0800 Subject: [PATCH] lazy Analysis (#2927) * lazy Analysis * lazy loading menu * remove hard code * fix style warning * fix loading height waring * fix offineData Loading error * lazy load menu * fix login test eroor * fix #2950 ,fix topmeun error * add args of puppeteer * add layout=topmenu e2e test --- jest-puppeteer.config.js | 14 + package.json | 1 + src/components/SiderMenu/BaseMenu.js | 10 +- src/components/SiderMenu/SiderMenu.js | 39 +- src/components/SiderMenu/SiderMenu.test.js | 2 +- src/components/SiderMenu/SiderMenuUtils.js | 39 ++ src/components/SiderMenu/index.js | 30 +- src/components/TopNavHeader/index.js | 10 +- src/e2e/home.e2e.js | 2 +- src/e2e/topMenu.e2e.js | 18 + src/layouts/BasicLayout.js | 51 +- src/pages/Dashboard/Analysis.js | 611 ++------------------- src/pages/Dashboard/IntroduceRow.js | 144 +++++ src/pages/Dashboard/OfflineData.js | 65 +++ src/pages/Dashboard/ProportionSales.js | 63 +++ src/pages/Dashboard/SalesCard.js | 150 +++++ src/pages/Dashboard/TopSearch.js | 111 ++++ 17 files changed, 708 insertions(+), 652 deletions(-) create mode 100644 jest-puppeteer.config.js create mode 100644 src/components/SiderMenu/SiderMenuUtils.js create mode 100644 src/e2e/topMenu.e2e.js create mode 100755 src/pages/Dashboard/IntroduceRow.js create mode 100755 src/pages/Dashboard/OfflineData.js create mode 100755 src/pages/Dashboard/ProportionSales.js create mode 100755 src/pages/Dashboard/SalesCard.js create mode 100755 src/pages/Dashboard/TopSearch.js diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js new file mode 100644 index 00000000..9edd60e1 --- /dev/null +++ b/jest-puppeteer.config.js @@ -0,0 +1,14 @@ +// ps https://github.com/GoogleChrome/puppeteer/issues/3120 +module.exports = { + launch: { + args: [ + '--disable-gpu', + '--disable-dev-shm-usage', + '--disable-setuid-sandbox', + '--no-first-run', + '--no-sandbox', + '--no-zygote', + '--single-process', + ], + }, +}; diff --git a/package.json b/package.json index fbd3efd1..74d3653a 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "react-document-title": "^2.0.3", "react-dom": "^16.5.1", "react-fittext": "^1.0.0", + "react-media": "^1.8.0", "react-router-dom": "^4.3.1" }, "devDependencies": { diff --git a/src/components/SiderMenu/BaseMenu.js b/src/components/SiderMenu/BaseMenu.js index 4629c292..8cf125ec 100644 --- a/src/components/SiderMenu/BaseMenu.js +++ b/src/components/SiderMenu/BaseMenu.js @@ -3,8 +3,8 @@ import { Menu, Icon } from 'antd'; import Link from 'umi/link'; import isEqual from 'lodash/isEqual'; import memoizeOne from 'memoize-one'; -import pathToRegexp from 'path-to-regexp'; import { urlToList } from '../_utils/pathTools'; +import { getMenuMatches } from './SiderMenuUtils'; import styles from './index.less'; const { SubMenu } = Menu; @@ -23,14 +23,6 @@ const getIcon = icon => { return icon; }; -export const getMenuMatches = (flatMenuKeys, path) => - flatMenuKeys.filter(item => { - if (item) { - return pathToRegexp(item).test(path); - } - return false; - }); - export default class BaseMenu extends PureComponent { constructor(props) { super(props); diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js index fcce20d2..e1b49d00 100644 --- a/src/components/SiderMenu/SiderMenu.js +++ b/src/components/SiderMenu/SiderMenu.js @@ -1,27 +1,14 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, Suspense } from 'react'; import { Layout } from 'antd'; import classNames from 'classnames'; import Link from 'umi/link'; import styles from './index.less'; -import BaseMenu, { getMenuMatches } from './BaseMenu'; -import { urlToList } from '../_utils/pathTools'; +import PageLoading from '../PageLoading'; +import { getDefaultCollapsedSubMenus } from './SiderMenuUtils'; +const BaseMenu = React.lazy(() => import('./BaseMenu')); const { Sider } = Layout; -/** - * 获得菜单子节点 - * @memberof SiderMenu - */ -const getDefaultCollapsedSubMenus = props => { - const { - location: { pathname }, - flatMenuKeys, - } = props; - return urlToList(pathname) - .map(item => getMenuMatches(flatMenuKeys, item)[0]) - .filter(item => item); -}; - export default class SiderMenu extends PureComponent { constructor(props) { super(props); @@ -67,7 +54,6 @@ export default class SiderMenu extends PureComponent { [styles.fixSiderbar]: fixSiderbar, [styles.light]: theme === 'light', }); - return ( Ant Design Pro - + }> + + ); } diff --git a/src/components/SiderMenu/SiderMenu.test.js b/src/components/SiderMenu/SiderMenu.test.js index 33a7c805..3d280da0 100644 --- a/src/components/SiderMenu/SiderMenu.test.js +++ b/src/components/SiderMenu/SiderMenu.test.js @@ -1,4 +1,4 @@ -import { getFlatMenuKeys } from './index'; +import { getFlatMenuKeys } from './SiderMenuUtils'; const menu = [ { diff --git a/src/components/SiderMenu/SiderMenuUtils.js b/src/components/SiderMenu/SiderMenuUtils.js new file mode 100644 index 00000000..6722ed7a --- /dev/null +++ b/src/components/SiderMenu/SiderMenuUtils.js @@ -0,0 +1,39 @@ +import pathToRegexp from 'path-to-regexp'; +import { urlToList } from '../_utils/pathTools'; + +/** + * Recursively flatten the data + * [{path:string},{path:string}] => {path,path2} + * @param menus + */ +export const getFlatMenuKeys = menuData => { + let keys = []; + menuData.forEach(item => { + keys.push(item.path); + if (item.children) { + keys = keys.concat(getFlatMenuKeys(item.children)); + } + }); + return keys; +}; + +export const getMenuMatches = (flatMenuKeys, path) => + flatMenuKeys.filter(item => { + if (item) { + return pathToRegexp(item).test(path); + } + return false; + }); +/** + * 获得菜单子节点 + * @memberof SiderMenu + */ +export const getDefaultCollapsedSubMenus = props => { + const { + location: { pathname }, + flatMenuKeys, + } = props; + return urlToList(pathname) + .map(item => getMenuMatches(flatMenuKeys, item)[0]) + .filter(item => item); +}; diff --git a/src/components/SiderMenu/index.js b/src/components/SiderMenu/index.js index cc911eb1..0be27331 100644 --- a/src/components/SiderMenu/index.js +++ b/src/components/SiderMenu/index.js @@ -1,25 +1,11 @@ import React from 'react'; import { Drawer } from 'antd'; import SiderMenu from './SiderMenu'; +import { getFlatMenuKeys } from './SiderMenuUtils'; -/** - * Recursively flatten the data - * [{path:string},{path:string}] => {path,path2} - * @param menus - */ -export const getFlatMenuKeys = menuData => { - let keys = []; - menuData.forEach(item => { - keys.push(item.path); - if (item.children) { - keys = keys.concat(getFlatMenuKeys(item.children)); - } - }); - return keys; -}; - -const SiderMenuWrapper = props => { +const SiderMenuWrapper = React.memo(props => { const { isMobile, menuData, collapsed, onCollapse } = props; + const flatMenuKeys = getFlatMenuKeys(menuData); return isMobile ? ( { height: '100vh', }} > - + ) : ( - + ); -}; +}); export default SiderMenuWrapper; diff --git a/src/components/TopNavHeader/index.js b/src/components/TopNavHeader/index.js index 560f01d8..ee302f66 100644 --- a/src/components/TopNavHeader/index.js +++ b/src/components/TopNavHeader/index.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import Link from 'umi/link'; import RightContent from '../GlobalHeader/RightContent'; import BaseMenu from '../SiderMenu/BaseMenu'; +import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils'; import styles from './index.less'; export default class TopNavHeader extends PureComponent { @@ -16,8 +17,9 @@ export default class TopNavHeader extends PureComponent { } render() { - const { theme, contentWidth, logo } = this.props; + const { theme, contentWidth, menuData, logo } = this.props; const { maxWidth } = this.state; + const flatMenuKeys = getFlatMenuKeys(menuData); return (
- +
diff --git a/src/e2e/home.e2e.js b/src/e2e/home.e2e.js index b8d22cb8..0531d5f4 100644 --- a/src/e2e/home.e2e.js +++ b/src/e2e/home.e2e.js @@ -7,7 +7,7 @@ describe('Homepage', () => { it('it should have logo text', async () => { await page.goto(BASE_URL); await page.waitForSelector('h1', { - timeout: 2000, + timeout: 5000, }); const text = await page.evaluate(() => document.getElementsByTagName('h1')[0].innerText); expect(text).toContain('Ant Design Pro'); diff --git a/src/e2e/topMenu.e2e.js b/src/e2e/topMenu.e2e.js new file mode 100644 index 00000000..51ff9f35 --- /dev/null +++ b/src/e2e/topMenu.e2e.js @@ -0,0 +1,18 @@ +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(); + }); +}); diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js index 4b43e4d6..3b7553f7 100644 --- a/src/layouts/BasicLayout.js +++ b/src/layouts/BasicLayout.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { Layout } from 'antd'; import DocumentTitle from 'react-document-title'; import isEqual from 'lodash/isEqual'; @@ -7,16 +7,19 @@ import { connect } from 'dva'; import { ContainerQuery } from 'react-container-query'; import classNames from 'classnames'; import pathToRegexp from 'path-to-regexp'; -import { enquireScreen, unenquireScreen } from 'enquire-js'; +import Media from 'react-media'; import { formatMessage } from 'umi/locale'; -import SiderMenu from '@/components/SiderMenu'; import Authorized from '@/utils/Authorized'; -import SettingDrawer from '@/components/SettingDrawer'; import logo from '../assets/logo.svg'; import Footer from './Footer'; import Header from './Header'; import Context from './MenuContext'; import Exception403 from '../pages/Exception/403'; +import PageLoading from '@/components/PageLoading'; +import SiderMenu from '@/components/SiderMenu'; + +// lazy load SettingDrawer +const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer')); const { Content } = Layout; @@ -88,8 +91,6 @@ class BasicLayout extends React.PureComponent { } state = { - rendering: true, - isMobile: false, menuData: this.getMenuData(), }; @@ -101,27 +102,13 @@ class BasicLayout extends React.PureComponent { dispatch({ type: 'setting/getSetting', }); - this.renderRef = requestAnimationFrame(() => { - this.setState({ - rendering: false, - }); - }); - this.enquireHandler = enquireScreen(mobile => { - const { isMobile } = this.state; - if (isMobile !== mobile) { - this.setState({ - isMobile: mobile, - }); - } - }); } componentDidUpdate(preProps) { // After changing to phone mode, // if collapsed is true, you need to click twice to display this.breadcrumbNameMap = this.getBreadcrumbNameMap(); - const { isMobile } = this.state; - const { collapsed } = this.props; + const { collapsed, isMobile } = this.props; if (isMobile && !preProps.isMobile && !collapsed) { this.handleMenuCollapse(false); } @@ -129,7 +116,6 @@ class BasicLayout extends React.PureComponent { componentWillUnmount() { cancelAnimationFrame(this.renderRef); - unenquireScreen(this.enquireHandler); } getContext() { @@ -187,8 +173,7 @@ class BasicLayout extends React.PureComponent { }; getLayoutStyle = () => { - const { isMobile } = this.state; - const { fixSiderbar, collapsed, layout } = this.props; + const { fixSiderbar, isMobile, collapsed, layout } = this.props; if (fixSiderbar && layout !== 'topmenu' && !isMobile) { return { paddingLeft: collapsed ? '80px' : '256px', @@ -213,15 +198,14 @@ class BasicLayout extends React.PureComponent { }); }; - renderSettingDrawer() { + renderSettingDrawer = () => { // Do not render SettingDrawer in production // unless it is deployed in preview.pro.ant.design as demo - const { rendering } = this.state; - if ((rendering || process.env.NODE_ENV === 'production') && APP_TYPE !== 'site') { + if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') { return null; } return ; - } + }; render() { const { @@ -229,8 +213,9 @@ class BasicLayout extends React.PureComponent { layout: PropsLayout, children, location: { pathname }, + isMobile, } = this.props; - const { isMobile, menuData } = this.state; + const { menuData } = this.state; const isTop = PropsLayout === 'topmenu'; const routerConfig = this.matchParamsPath(pathname); const layout = ( @@ -282,7 +267,7 @@ class BasicLayout extends React.PureComponent { )} - {this.renderSettingDrawer()} + }>{this.renderSettingDrawer()} ); } @@ -292,4 +277,8 @@ export default connect(({ global, setting }) => ({ collapsed: global.collapsed, layout: setting.layout, ...setting, -}))(BasicLayout); +}))(props => ( + + {isMobile => } + +)); diff --git a/src/pages/Dashboard/Analysis.js b/src/pages/Dashboard/Analysis.js index 0c4a549a..58261ebb 100644 --- a/src/pages/Dashboard/Analysis.js +++ b/src/pages/Dashboard/Analysis.js @@ -1,70 +1,28 @@ -import React, { Component } from 'react'; +import React, { Component, Suspense } from 'react'; import { connect } from 'dva'; -import { formatMessage, FormattedMessage } from 'umi/locale'; -import { - Row, - Col, - Icon, - Card, - Tabs, - Table, - Radio, - DatePicker, - Tooltip, - Menu, - Dropdown, -} from 'antd'; -import { - ChartCard, - MiniArea, - MiniBar, - MiniProgress, - Field, - Bar, - Pie, - TimelineChart, -} from '@/components/Charts'; -import Trend from '@/components/Trend'; -import NumberInfo from '@/components/NumberInfo'; -import numeral from 'numeral'; +import { Row, Col, Icon, Menu, Dropdown } from 'antd'; + import GridContent from '@/components/PageHeaderWrapper/GridContent'; -import Yuan from '@/utils/Yuan'; import { getTimeDistance } from '@/utils/utils'; import styles from './Analysis.less'; +import PageLoading from '@/components/PageLoading'; -const { TabPane } = Tabs; -const { RangePicker } = DatePicker; - -const rankingListData = []; -for (let i = 0; i < 7; i += 1) { - rankingListData.push({ - title: `工专路 ${i} 号店`, - total: 323234, - }); -} +const IntroduceRow = React.lazy(() => import('./IntroduceRow')); +const SalesCard = React.lazy(() => import('./SalesCard')); +const TopSearch = React.lazy(() => import('./TopSearch')); +const ProportionSales = React.lazy(() => import('./ProportionSales')); +const OfflineData = React.lazy(() => import('./OfflineData')); @connect(({ chart, loading }) => ({ chart, loading: loading.effects['chart/fetch'], })) class Analysis extends Component { - constructor(props) { - super(props); - this.rankingListData = []; - for (let i = 0; i < 7; i += 1) { - this.rankingListData.push({ - title: formatMessage({ id: 'app.analysis.test' }, { no: i }), - total: 323234, - }); - } - } - state = { salesType: 'all', currentTabKey: '', rangePickerValue: getTimeDistance('year'), - loading: true, }; componentDidMount() { @@ -73,11 +31,6 @@ class Analysis extends Component { dispatch({ type: 'chart/fetch', }); - this.timeoutId = setTimeout(() => { - this.setState({ - loading: false, - }); - }, 600); }); } @@ -124,7 +77,7 @@ class Analysis extends Component { }); }; - isActive(type) { + isActive = type => { const { rangePickerValue } = this.state; const value = getTimeDistance(type); if (!rangePickerValue[0] || !rangePickerValue[1]) { @@ -137,11 +90,11 @@ class Analysis extends Component { return styles.currentDate; } return ''; - } + }; render() { - const { rangePickerValue, salesType, loading: stateLoading, currentTabKey } = this.state; - const { chart, loading: propsLoading } = this.props; + const { rangePickerValue, salesType, currentTabKey } = this.state; + const { chart, loading } = this.props; const { visitData, visitData2, @@ -153,7 +106,6 @@ class Analysis extends Component { salesTypeDataOnline, salesTypeDataOffline, } = chart; - const loading = propsLoading || stateLoading; let salesPieData; if (salesType === 'all') { salesPieData = salesTypeData; @@ -167,7 +119,7 @@ class Analysis extends Component { ); - const iconGroup = ( + const dropdownGroup = ( @@ -175,515 +127,56 @@ class Analysis extends Component { ); - const salesExtra = ( - - ); - - const columns = [ - { - title: , - dataIndex: 'index', - key: 'index', - }, - { - title: ( - - ), - dataIndex: 'keyword', - key: 'keyword', - render: text => {text}, - }, - { - title: , - dataIndex: 'count', - key: 'count', - sorter: (a, b) => a.count - b.count, - className: styles.alignRight, - }, - { - title: ( - - ), - dataIndex: 'range', - key: 'range', - sorter: (a, b) => a.range - b.range, - render: (text, record) => ( - - {text}% - - ), - align: 'right', - }, - ]; - const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name); - const CustomTab = ({ data, currentTabKey: currentKey }) => ( - - - - } - gap={2} - total={`${data.cvr * 100}%`} - theme={currentKey !== data.name && 'light'} - /> - - - - - - ); - - const topColResponsiveProps = { - xs: 24, - sm: 12, - md: 12, - lg: 12, - xl: 6, - style: { marginBottom: 24 }, - }; - return ( - - - - } - action={ - - } - > - - - } - loading={loading} - total={() => 126560} - footer={ - - } - value={`¥${numeral(12423).format('0,0')}`} - /> - } - contentHeight={46} - > - - - 12% - - - - 11% - - - - - - } - action={ - - } - > - - - } - total={numeral(8846).format('0,0')} - footer={ - - } - value={numeral(1234).format('0,0')} - /> - } - contentHeight={46} - > - - - - - } - action={ - - } - > - - - } - total={numeral(6560).format('0,0')} - footer={ - - } - value="60%" - /> - } - contentHeight={46} - > - - - - - - } - action={ - - } - > - - - } - total="78%" - footer={ -
- - - 12% - - - - 11% - -
- } - contentHeight={46} - > - -
- -
- - -
- - } - key="sales" - > - - -
- - } - data={salesData} - /> -
- - -
-

- -

-
    - {this.rankingListData.map((item, i) => ( -
  • - - {i + 1} - - - {item.title} - - - {numeral(item.total).format('0,0')} - -
  • - ))} -
-
- -
-
- } - key="views" - > - - -
- - } - data={salesData} - /> -
- - -
-

- -

-
    - {this.rankingListData.map((item, i) => ( -
  • - - {i + 1} - - - {item.title} - - {numeral(item.total).format('0,0')} -
  • - ))} -
-
- -
-
-
-
-
- + }> + + + + + - - } - extra={iconGroup} - style={{ marginTop: 24 }} - > - - - - - - } - > - - - - } - gap={8} - total={numeral(12321).format('0,0')} - status="up" - subTotal={17.1} - /> - - - - - - - } - > - - - - } - total={2.7} - status="down" - subTotal={26.2} - gap={8} - /> - - - - record.index} - size="small" - columns={columns} - dataSource={searchData} - pagination={{ - style: { marginBottom: 0 }, - pageSize: 5, - }} + + - + - - } - bodyStyle={{ padding: 24 }} - extra={ -
- {iconGroup} -
- - - - - - - - - - - -
-
- } - style={{ marginTop: 24, minHeight: 509 }} - > -

- -

- } - total={() => {salesPieData.reduce((pre, now) => now.y + pre, 0)}} - data={salesPieData} - valueFormat={value => {value}} - height={248} - lineWidth={4} + + -
+ - - - - {offlineData.map(shop => ( - } key={shop.name}> -
- -
-
- ))} -
-
+ + + ); } diff --git a/src/pages/Dashboard/IntroduceRow.js b/src/pages/Dashboard/IntroduceRow.js new file mode 100755 index 00000000..e8f763f3 --- /dev/null +++ b/src/pages/Dashboard/IntroduceRow.js @@ -0,0 +1,144 @@ +import React, { memo } from 'react'; +import { Row, Col, Icon, Tooltip } from 'antd'; +import { FormattedMessage } from 'umi/locale'; +import styles from './Analysis.less'; +import { ChartCard, MiniArea, MiniBar, MiniProgress, Field } from '@/components/Charts'; +import Trend from '@/components/Trend'; +import numeral from 'numeral'; +import Yuan from '@/utils/Yuan'; + +const topColResponsiveProps = { + xs: 24, + sm: 12, + md: 12, + lg: 12, + xl: 6, + style: { marginBottom: 24 }, +}; + +const IntroduceRow = memo(({ loading, visitData }) => ( + + + } + action={ + } + > + + + } + loading={loading} + total={() => 126560} + footer={ + } + value={`¥${numeral(12423).format('0,0')}`} + /> + } + contentHeight={46} + > + + + 12% + + + + 11% + + + + + + } + action={ + } + > + + + } + total={numeral(8846).format('0,0')} + footer={ + } + value={numeral(1234).format('0,0')} + /> + } + contentHeight={46} + > + + + + + } + action={ + } + > + + + } + total={numeral(6560).format('0,0')} + footer={ + + } + value="60%" + /> + } + contentHeight={46} + > + + + + + + } + action={ + } + > + + + } + total="78%" + footer={ +
+ + + 12% + + + + 11% + +
+ } + contentHeight={26} + > + +
+ + +)); + +export default IntroduceRow; diff --git a/src/pages/Dashboard/OfflineData.js b/src/pages/Dashboard/OfflineData.js new file mode 100755 index 00000000..f7d06ef0 --- /dev/null +++ b/src/pages/Dashboard/OfflineData.js @@ -0,0 +1,65 @@ +import React, { memo } from 'react'; +import { Card, Tabs, Row, Col } from 'antd'; +import { formatMessage, FormattedMessage } from 'umi/locale'; +import styles from './Analysis.less'; +import { TimelineChart, Pie } from '@/components/Charts'; +import NumberInfo from '@/components/NumberInfo'; + +const CustomTab = ({ data, currentTabKey: currentKey }) => ( + + + + } + gap={2} + total={`${data.cvr * 100}%`} + theme={currentKey !== data.name && 'light'} + /> + + + + + +); + +const { TabPane } = Tabs; + +const OfflineData = memo( + ({ activeKey, loading, offlineData, offlineChartData, handleTabChange }) => ( + + + {offlineData.map(shop => ( + } key={shop.name}> +
+ +
+
+ ))} +
+
+ ) +); + +export default OfflineData; diff --git a/src/pages/Dashboard/ProportionSales.js b/src/pages/Dashboard/ProportionSales.js new file mode 100755 index 00000000..6343b71c --- /dev/null +++ b/src/pages/Dashboard/ProportionSales.js @@ -0,0 +1,63 @@ +import React, { memo } from 'react'; +import { Card, Radio } from 'antd'; +import { FormattedMessage } from 'umi/locale'; +import styles from './Analysis.less'; +import { Pie } from '@/components/Charts'; +import Yuan from '@/utils/Yuan'; + +const ProportionSales = memo( + ({ dropdownGroup, salesType, loading, salesPieData, handleChangeSalesType }) => ( + + } + bodyStyle={{ padding: 24 }} + extra={ +
+ {dropdownGroup} +
+ + + + + + + + + + + +
+
+ } + style={{ marginTop: 24 }} + > +
+

+ +

+ } + total={() => {salesPieData.reduce((pre, now) => now.y + pre, 0)}} + data={salesPieData} + valueFormat={value => {value}} + height={248} + lineWidth={4} + /> +
+
+ ) +); + +export default ProportionSales; diff --git a/src/pages/Dashboard/SalesCard.js b/src/pages/Dashboard/SalesCard.js new file mode 100755 index 00000000..3ab57775 --- /dev/null +++ b/src/pages/Dashboard/SalesCard.js @@ -0,0 +1,150 @@ +import React, { memo } from 'react'; +import { Row, Col, Card, Tabs, DatePicker } from 'antd'; +import { FormattedMessage, formatMessage } from 'umi/locale'; +import numeral from 'numeral'; +import styles from './Analysis.less'; +import { Bar } from '@/components/Charts'; + +const { RangePicker } = DatePicker; +const { TabPane } = Tabs; + +const rankingListData = []; +for (let i = 0; i < 7; i += 1) { + rankingListData.push({ + title: formatMessage({ id: 'app.analysis.test' }, { no: i }), + total: 323234, + }); +} + +const SalesCard = memo( + ({ rangePickerValue, salesData, isActive, handleRangePickerChange, loading, selectDate }) => ( + + + } + size="large" + tabBarStyle={{ marginBottom: 24 }} + > + } + key="sales" + > + +
+
+ + } + data={salesData} + /> +
+ + +
+

+ +

+
    + {rankingListData.map((item, i) => ( +
  • + + {i + 1} + + + {item.title} + + + {numeral(item.total).format('0,0')} + +
  • + ))} +
+
+ + + + } + key="views" + > + + +
+ + } + data={salesData} + /> +
+ + +
+

+ +

+
    + {rankingListData.map((item, i) => ( +
  • + + {i + 1} + + + {item.title} + + {numeral(item.total).format('0,0')} +
  • + ))} +
+
+ + + + + + + ) +); + +export default SalesCard; diff --git a/src/pages/Dashboard/TopSearch.js b/src/pages/Dashboard/TopSearch.js new file mode 100755 index 00000000..4e75ea7d --- /dev/null +++ b/src/pages/Dashboard/TopSearch.js @@ -0,0 +1,111 @@ +import React, { memo } from 'react'; +import { Row, Col, Table, Tooltip, Card, Icon } from 'antd'; +import { FormattedMessage } from 'umi/locale'; +import Trend from '@/components/Trend'; +import numeral from 'numeral'; +import styles from './Analysis.less'; +import NumberInfo from '@/components/NumberInfo'; +import { MiniArea } from '@/components/Charts'; + +const columns = [ + { + title: , + dataIndex: 'index', + key: 'index', + }, + { + title: ( + + ), + dataIndex: 'keyword', + key: 'keyword', + render: text => {text}, + }, + { + title: , + dataIndex: 'count', + key: 'count', + sorter: (a, b) => a.count - b.count, + className: styles.alignRight, + }, + { + title: , + dataIndex: 'range', + key: 'range', + sorter: (a, b) => a.range - b.range, + render: (text, record) => ( + + {text}% + + ), + align: 'right', + }, +]; + +const TopSearch = memo(({ loading, visitData2, searchData, dropdownGroup }) => ( + + } + extra={dropdownGroup} + style={{ marginTop: 24 }} + > + + + + + } + > + + + + } + gap={8} + total={numeral(12321).format('0,0')} + status="up" + subTotal={17.1} + /> + + + + + + } + > + + + + } + total={2.7} + status="down" + subTotal={26.2} + gap={8} + /> + + + +
record.index} + size="small" + columns={columns} + dataSource={searchData} + pagination={{ + style: { marginBottom: 0 }, + pageSize: 5, + }} + /> + +)); + +export default TopSearch; -- GitLab