diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..9edd60e1dfd703234494ac93fcc9788eef364949
--- /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 fbd3efd19c857b34cf4d0bf4d2942c3c8b667a21..74d3653a1a7bb5e01d1a1584e6fff52b082a4a2b 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 4629c292c6846f574fffca32264609e25c39a2a0..8cf125ec3f4e69aecf70ce7097e3ca1341b54c80 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 fcce20d2ab5ac85e598d7e62a84d1a4278959891..e1b49d0037e20972f044e2691e00869fd01a0ed1 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 33a7c80572a3e224ceeb84facd9d1f06d26bcb59..3d280da08a8510cf056d46d9e239fbbdacc44001 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 0000000000000000000000000000000000000000..6722ed7a8544c6e19e46f157cf7a14aca27feb3b
--- /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 cc911eb1e203e72dfb9edff0158a21cc4208e722..0be273314d593c01b54cd127b39c97d55833a990 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 560f01d8fc5481a2e7e641c71f012c638f77959a..ee302f6625ad8a4325cb1fa5c4c852e85fec7644 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 b8d22cb86ec1bda3adda7944666ceb6adb22e255..0531d5f4b549d477d51f980df92b016bf8e00cc2 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 0000000000000000000000000000000000000000..51ff9f35c632e3c6471b153b45f76b40e9a11cc9
--- /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 4b43e4d6961f9a842e83bab531711aad146211c8..3b7553f76eb041a50d91651246ee4d42809fbf39 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 0c4a549afaa29808b990bb2c1c21408499be332d..58261ebb3b337bd4bd41581df4a847db3bc74b83 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 0000000000000000000000000000000000000000..e8f763f3a6163a0867084a1bfee9c7e80a536fda
--- /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 0000000000000000000000000000000000000000..f7d06ef07b8f10f93a86320b3b17d081816a462f
--- /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 0000000000000000000000000000000000000000..6343b71c7b8aee4e062216edba976c1c188815f2
--- /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 0000000000000000000000000000000000000000..3ab577757067c329a79028cbec08deb3dfc61787
--- /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 0000000000000000000000000000000000000000..4e75ea7d51ebba6a5d52e336c24d318f8e5d5a41
--- /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;