Analysis.js 15 KB

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