动态菜单和动态路由的 antd pro

Analysis.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. import React, { Component, Fragment } from 'react';
  2. import { connect } from 'dva';
  3. import {
  4. Row,
  5. Col,
  6. Icon,
  7. Card,
  8. Tabs,
  9. Table,
  10. Radio,
  11. DatePicker,
  12. Tooltip,
  13. Menu,
  14. Dropdown,
  15. } from 'antd';
  16. import numeral from 'numeral';
  17. import {
  18. ChartCard,
  19. yuan,
  20. MiniArea,
  21. MiniBar,
  22. MiniProgress,
  23. Field,
  24. Bar,
  25. Pie,
  26. TimelineChart,
  27. } from 'components/Charts';
  28. import Trend from 'components/Trend';
  29. import NumberInfo from 'components/NumberInfo';
  30. import { getTimeDistance } from '../../utils/utils';
  31. import styles from './Analysis.less';
  32. const { TabPane } = Tabs;
  33. const { RangePicker } = DatePicker;
  34. const rankingListData = [];
  35. for (let i = 0; i < 7; i += 1) {
  36. rankingListData.push({
  37. title: `工专路 ${i} 号店`,
  38. total: 323234,
  39. });
  40. }
  41. @connect(({ chart, loading }) => ({
  42. chart,
  43. loading: loading.effects['chart/fetch'],
  44. }))
  45. export default class Analysis extends Component {
  46. state = {
  47. salesType: 'all',
  48. currentTabKey: '',
  49. rangePickerValue: getTimeDistance('year'),
  50. };
  51. componentDidMount() {
  52. this.props.dispatch({
  53. type: 'chart/fetch',
  54. });
  55. }
  56. componentWillUnmount() {
  57. const { dispatch } = this.props;
  58. dispatch({
  59. type: 'chart/clear',
  60. });
  61. }
  62. handleChangeSalesType = e => {
  63. this.setState({
  64. salesType: e.target.value,
  65. });
  66. };
  67. handleTabChange = key => {
  68. this.setState({
  69. currentTabKey: key,
  70. });
  71. };
  72. handleRangePickerChange = rangePickerValue => {
  73. this.setState({
  74. rangePickerValue,
  75. });
  76. this.props.dispatch({
  77. type: 'chart/fetchSalesData',
  78. });
  79. };
  80. selectDate = type => {
  81. this.setState({
  82. rangePickerValue: getTimeDistance(type),
  83. });
  84. this.props.dispatch({
  85. type: 'chart/fetchSalesData',
  86. });
  87. };
  88. isActive(type) {
  89. const { rangePickerValue } = this.state;
  90. const value = getTimeDistance(type);
  91. if (!rangePickerValue[0] || !rangePickerValue[1]) {
  92. return;
  93. }
  94. if (
  95. rangePickerValue[0].isSame(value[0], 'day') &&
  96. rangePickerValue[1].isSame(value[1], 'day')
  97. ) {
  98. return styles.currentDate;
  99. }
  100. }
  101. render() {
  102. const { rangePickerValue, salesType, currentTabKey } = this.state;
  103. const { chart, loading } = this.props;
  104. const {
  105. visitData,
  106. visitData2,
  107. salesData,
  108. searchData,
  109. offlineData,
  110. offlineChartData,
  111. salesTypeData,
  112. salesTypeDataOnline,
  113. salesTypeDataOffline,
  114. } = chart;
  115. const salesPieData =
  116. salesType === 'all'
  117. ? salesTypeData
  118. : salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
  119. const menu = (
  120. <Menu>
  121. <Menu.Item>操作一</Menu.Item>
  122. <Menu.Item>操作二</Menu.Item>
  123. </Menu>
  124. );
  125. const iconGroup = (
  126. <span className={styles.iconGroup}>
  127. <Dropdown overlay={menu} placement="bottomRight">
  128. <Icon type="ellipsis" />
  129. </Dropdown>
  130. </span>
  131. );
  132. const salesExtra = (
  133. <div className={styles.salesExtraWrap}>
  134. <div className={styles.salesExtra}>
  135. <a className={this.isActive('today')} onClick={() => this.selectDate('today')}>
  136. 今日
  137. </a>
  138. <a className={this.isActive('week')} onClick={() => this.selectDate('week')}>
  139. 本周
  140. </a>
  141. <a className={this.isActive('month')} onClick={() => this.selectDate('month')}>
  142. 本月
  143. </a>
  144. <a className={this.isActive('year')} onClick={() => this.selectDate('year')}>
  145. 全年
  146. </a>
  147. </div>
  148. <RangePicker
  149. value={rangePickerValue}
  150. onChange={this.handleRangePickerChange}
  151. style={{ width: 256 }}
  152. />
  153. </div>
  154. );
  155. const columns = [
  156. {
  157. title: '排名',
  158. dataIndex: 'index',
  159. key: 'index',
  160. },
  161. {
  162. title: '搜索关键词',
  163. dataIndex: 'keyword',
  164. key: 'keyword',
  165. render: text => <a href="/">{text}</a>,
  166. },
  167. {
  168. title: '用户数',
  169. dataIndex: 'count',
  170. key: 'count',
  171. sorter: (a, b) => a.count - b.count,
  172. className: styles.alignRight,
  173. },
  174. {
  175. title: '周涨幅',
  176. dataIndex: 'range',
  177. key: 'range',
  178. sorter: (a, b) => a.range - b.range,
  179. render: (text, record) => (
  180. <Trend flag={record.status === 1 ? 'down' : 'up'}>
  181. <span style={{ marginRight: 4 }}>{text}%</span>
  182. </Trend>
  183. ),
  184. align: 'right',
  185. },
  186. ];
  187. const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);
  188. const CustomTab = ({ data, currentTabKey: currentKey }) => (
  189. <Row gutter={8} style={{ width: 138, margin: '8px 0' }}>
  190. <Col span={12}>
  191. <NumberInfo
  192. title={data.name}
  193. subTitle="转化率"
  194. gap={2}
  195. total={`${data.cvr * 100}%`}
  196. theme={currentKey !== data.name && 'light'}
  197. />
  198. </Col>
  199. <Col span={12} style={{ paddingTop: 36 }}>
  200. <Pie
  201. animate={false}
  202. color={currentKey !== data.name && '#BDE4FF'}
  203. inner={0.55}
  204. tooltip={false}
  205. margin={[0, 0, 0, 0]}
  206. percent={data.cvr * 100}
  207. height={64}
  208. />
  209. </Col>
  210. </Row>
  211. );
  212. const topColResponsiveProps = {
  213. xs: 24,
  214. sm: 12,
  215. md: 12,
  216. lg: 12,
  217. xl: 6,
  218. style: { marginBottom: 24 },
  219. };
  220. return (
  221. <Fragment>
  222. <Row gutter={24}>
  223. <Col {...topColResponsiveProps}>
  224. <ChartCard
  225. bordered={false}
  226. title="总销售额"
  227. action={
  228. <Tooltip title="指标说明">
  229. <Icon type="info-circle-o" />
  230. </Tooltip>
  231. }
  232. total={() => <span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />}
  233. footer={<Field label="日均销售额" value={`¥${numeral(12423).format('0,0')}`} />}
  234. contentHeight={46}
  235. >
  236. <Trend flag="up" style={{ marginRight: 16 }}>
  237. 周同比<span className={styles.trendText}>12%</span>
  238. </Trend>
  239. <Trend flag="down">
  240. 日环比<span className={styles.trendText}>11%</span>
  241. </Trend>
  242. </ChartCard>
  243. </Col>
  244. <Col {...topColResponsiveProps}>
  245. <ChartCard
  246. bordered={false}
  247. title="访问量"
  248. action={
  249. <Tooltip title="指标说明">
  250. <Icon type="info-circle-o" />
  251. </Tooltip>
  252. }
  253. total={numeral(8846).format('0,0')}
  254. footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
  255. contentHeight={46}
  256. >
  257. <MiniArea color="#975FE4" data={visitData} />
  258. </ChartCard>
  259. </Col>
  260. <Col {...topColResponsiveProps}>
  261. <ChartCard
  262. bordered={false}
  263. title="支付笔数"
  264. action={
  265. <Tooltip title="指标说明">
  266. <Icon type="info-circle-o" />
  267. </Tooltip>
  268. }
  269. total={numeral(6560).format('0,0')}
  270. footer={<Field label="转化率" value="60%" />}
  271. contentHeight={46}
  272. >
  273. <MiniBar data={visitData} />
  274. </ChartCard>
  275. </Col>
  276. <Col {...topColResponsiveProps}>
  277. <ChartCard
  278. bordered={false}
  279. title="运营活动效果"
  280. action={
  281. <Tooltip title="指标说明">
  282. <Icon type="info-circle-o" />
  283. </Tooltip>
  284. }
  285. total="78%"
  286. footer={
  287. <div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
  288. <Trend flag="up" style={{ marginRight: 16 }}>
  289. 周同比<span className={styles.trendText}>12%</span>
  290. </Trend>
  291. <Trend flag="down">
  292. 日环比<span className={styles.trendText}>11%</span>
  293. </Trend>
  294. </div>
  295. }
  296. contentHeight={46}
  297. >
  298. <MiniProgress percent={78} strokeWidth={8} target={80} color="#13C2C2" />
  299. </ChartCard>
  300. </Col>
  301. </Row>
  302. <Card loading={loading} bordered={false} bodyStyle={{ padding: 0 }}>
  303. <div className={styles.salesCard}>
  304. <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}>
  305. <TabPane tab="销售额" key="sales">
  306. <Row>
  307. <Col xl={16} lg={12} md={12} sm={24} xs={24}>
  308. <div className={styles.salesBar}>
  309. <Bar height={295} title="销售额趋势" data={salesData} />
  310. </div>
  311. </Col>
  312. <Col xl={8} lg={12} md={12} sm={24} xs={24}>
  313. <div className={styles.salesRank}>
  314. <h4 className={styles.rankingTitle}>门店销售额排名</h4>
  315. <ul className={styles.rankingList}>
  316. {rankingListData.map((item, i) => (
  317. <li key={item.title}>
  318. <span className={i < 3 ? styles.active : ''}>{i + 1}</span>
  319. <span>{item.title}</span>
  320. <span>{numeral(item.total).format('0,0')}</span>
  321. </li>
  322. ))}
  323. </ul>
  324. </div>
  325. </Col>
  326. </Row>
  327. </TabPane>
  328. <TabPane tab="访问量" key="views">
  329. <Row>
  330. <Col xl={16} lg={12} md={12} sm={24} xs={24}>
  331. <div className={styles.salesBar}>
  332. <Bar height={292} title="访问量趋势" data={salesData} />
  333. </div>
  334. </Col>
  335. <Col xl={8} lg={12} md={12} sm={24} xs={24}>
  336. <div className={styles.salesRank}>
  337. <h4 className={styles.rankingTitle}>门店访问量排名</h4>
  338. <ul className={styles.rankingList}>
  339. {rankingListData.map((item, i) => (
  340. <li key={item.title}>
  341. <span className={i < 3 ? styles.active : ''}>{i + 1}</span>
  342. <span>{item.title}</span>
  343. <span>{numeral(item.total).format('0,0')}</span>
  344. </li>
  345. ))}
  346. </ul>
  347. </div>
  348. </Col>
  349. </Row>
  350. </TabPane>
  351. </Tabs>
  352. </div>
  353. </Card>
  354. <Row gutter={24}>
  355. <Col xl={12} lg={24} md={24} sm={24} xs={24}>
  356. <Card
  357. loading={loading}
  358. bordered={false}
  359. title="线上热门搜索"
  360. extra={iconGroup}
  361. style={{ marginTop: 24 }}
  362. >
  363. <Row gutter={68}>
  364. <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
  365. <NumberInfo
  366. subTitle={
  367. <span>
  368. 搜索用户数
  369. <Tooltip title="指标文案">
  370. <Icon style={{ marginLeft: 8 }} type="info-circle-o" />
  371. </Tooltip>
  372. </span>
  373. }
  374. gap={8}
  375. total={numeral(12321).format('0,0')}
  376. status="up"
  377. subTotal={17.1}
  378. />
  379. <MiniArea line height={45} data={visitData2} />
  380. </Col>
  381. <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
  382. <NumberInfo
  383. subTitle="人均搜索次数"
  384. total={2.7}
  385. status="down"
  386. subTotal={26.2}
  387. gap={8}
  388. />
  389. <MiniArea line height={45} data={visitData2} />
  390. </Col>
  391. </Row>
  392. <Table
  393. rowKey={record => record.index}
  394. size="small"
  395. columns={columns}
  396. dataSource={searchData}
  397. pagination={{
  398. style: { marginBottom: 0 },
  399. pageSize: 5,
  400. }}
  401. />
  402. </Card>
  403. </Col>
  404. <Col xl={12} lg={24} md={24} sm={24} xs={24}>
  405. <Card
  406. loading={loading}
  407. className={styles.salesCard}
  408. bordered={false}
  409. title="销售额类别占比"
  410. bodyStyle={{ padding: 24 }}
  411. extra={
  412. <div className={styles.salesCardExtra}>
  413. {iconGroup}
  414. <div className={styles.salesTypeRadio}>
  415. <Radio.Group value={salesType} onChange={this.handleChangeSalesType}>
  416. <Radio.Button value="all">全部渠道</Radio.Button>
  417. <Radio.Button value="online">线上</Radio.Button>
  418. <Radio.Button value="offline">门店</Radio.Button>
  419. </Radio.Group>
  420. </div>
  421. </div>
  422. }
  423. style={{ marginTop: 24, minHeight: 509 }}
  424. >
  425. <h4 style={{ marginTop: 8, marginBottom: 32 }}>销售额</h4>
  426. <Pie
  427. hasLegend
  428. subTitle="销售额"
  429. total={() => (
  430. <span
  431. dangerouslySetInnerHTML={{
  432. __html: yuan(salesPieData.reduce((pre, now) => now.y + pre, 0)),
  433. }}
  434. />
  435. )}
  436. data={salesPieData}
  437. valueFormat={val => <span dangerouslySetInnerHTML={{ __html: yuan(val) }} />}
  438. height={248}
  439. lineWidth={4}
  440. />
  441. </Card>
  442. </Col>
  443. </Row>
  444. <Card
  445. loading={loading}
  446. className={styles.offlineCard}
  447. bordered={false}
  448. bodyStyle={{ padding: '0 0 32px 0' }}
  449. style={{ marginTop: 32 }}
  450. >
  451. <Tabs activeKey={activeKey} onChange={this.handleTabChange}>
  452. {offlineData.map(shop => (
  453. <TabPane tab={<CustomTab data={shop} currentTabKey={activeKey} />} key={shop.name}>
  454. <div style={{ padding: '0 24px' }}>
  455. <TimelineChart
  456. height={400}
  457. data={offlineChartData}
  458. titleMap={{ y1: '客流量', y2: '支付笔数' }}
  459. />
  460. </div>
  461. </TabPane>
  462. ))}
  463. </Tabs>
  464. </Card>
  465. </Fragment>
  466. );
  467. }
  468. }