Analysis.js 14.9 KB
Newer Older
陈帅's avatar
陈帅 committed
1
import React, { Component, Fragment } from 'react';
2
import { connect } from 'dva';
niko's avatar
niko committed
3 4 5 6 7 8 9 10 11 12 13 14 15
import {
  Row,
  Col,
  Icon,
  Card,
  Tabs,
  Table,
  Radio,
  DatePicker,
  Tooltip,
  Menu,
  Dropdown,
} from 'antd';
16
import numeral from 'numeral';
偏右's avatar
偏右 committed
17
import {
niko's avatar
niko committed
18 19 20 21 22 23 24 25 26
  ChartCard,
  yuan,
  MiniArea,
  MiniBar,
  MiniProgress,
  Field,
  Bar,
  Pie,
  TimelineChart,
niko's avatar
niko committed
27 28 29
} from 'components/Charts';
import Trend from 'components/Trend';
import NumberInfo from 'components/NumberInfo';
30 31 32 33
import { getTimeDistance } from '../../utils/utils';

import styles from './Analysis.less';

afc163's avatar
afc163 committed
34
const { TabPane } = Tabs;
35 36 37 38 39 40 41 42 43 44
const { RangePicker } = DatePicker;

const rankingListData = [];
for (let i = 0; i < 7; i += 1) {
  rankingListData.push({
    title: `工专路 ${i} 号店`,
    total: 323234,
  });
}

afc163's avatar
afc163 committed
45
const Yuan = ({ children }) => (
陈帅's avatar
陈帅 committed
46
  <span
47 48
    dangerouslySetInnerHTML={{ __html: yuan(children) }} /* eslint-disable-line react/no-danger */
  />
afc163's avatar
afc163 committed
49 50
);

Andreas Cederström's avatar
Andreas Cederström committed
51 52 53
@connect(({ chart, loading }) => ({
  chart,
  loading: loading.effects['chart/fetch'],
54 55 56 57 58
}))
export default class Analysis extends Component {
  state = {
    salesType: 'all',
    currentTabKey: '',
afc163's avatar
afc163 committed
59
    rangePickerValue: getTimeDistance('year'),
niko's avatar
niko committed
60
  };
61 62

  componentDidMount() {
63 64
    const { dispatch } = this.props;
    dispatch({
65
      type: 'chart/fetch',
66
    });
67 68 69 70 71 72 73 74 75
  }

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'chart/clear',
    });
  }

jim's avatar
jim committed
76
  handleChangeSalesType = e => {
77 78 79
    this.setState({
      salesType: e.target.value,
    });
niko's avatar
niko committed
80
  };
81

jim's avatar
jim committed
82
  handleTabChange = key => {
83 84 85
    this.setState({
      currentTabKey: key,
    });
niko's avatar
niko committed
86
  };
87

jim's avatar
jim committed
88
  handleRangePickerChange = rangePickerValue => {
89 90 91
    this.setState({
      rangePickerValue,
    });
nikogu's avatar
nikogu committed
92

93 94
    const { dispatch } = this.props;
    dispatch({
nikogu's avatar
nikogu committed
95 96
      type: 'chart/fetchSalesData',
    });
niko's avatar
niko committed
97
  };
98

jim's avatar
jim committed
99
  selectDate = type => {
100 101 102 103
    this.setState({
      rangePickerValue: getTimeDistance(type),
    });

104 105
    const { dispatch } = this.props;
    dispatch({
106 107
      type: 'chart/fetchSalesData',
    });
niko's avatar
niko committed
108
  };
109

afc163's avatar
afc163 committed
110 111 112 113 114 115
  isActive(type) {
    const { rangePickerValue } = this.state;
    const value = getTimeDistance(type);
    if (!rangePickerValue[0] || !rangePickerValue[1]) {
      return;
    }
niko's avatar
niko committed
116 117 118 119
    if (
      rangePickerValue[0].isSame(value[0], 'day') &&
      rangePickerValue[1].isSame(value[1], 'day')
    ) {
afc163's avatar
afc163 committed
120 121 122 123
      return styles.currentDate;
    }
  }

124
  render() {
125
    const { rangePickerValue, salesType, currentTabKey } = this.state;
Andreas Cederström's avatar
Andreas Cederström committed
126
    const { chart, loading } = this.props;
127 128
    const {
      visitData,
afc163's avatar
afc163 committed
129
      visitData2,
130 131 132 133 134 135 136
      salesData,
      searchData,
      offlineData,
      offlineChartData,
      salesTypeData,
      salesTypeDataOnline,
      salesTypeDataOffline,
afc163's avatar
afc163 committed
137
    } = chart;
138

niko's avatar
niko committed
139 140 141
    const salesPieData =
      salesType === 'all'
        ? salesTypeData
陈帅's avatar
陈帅 committed
142 143 144
        : salesType === 'online'
          ? salesTypeDataOnline
          : salesTypeDataOffline;
145

afc163's avatar
afc163 committed
146 147 148 149 150 151 152
    const menu = (
      <Menu>
        <Menu.Item>操作一</Menu.Item>
        <Menu.Item>操作二</Menu.Item>
      </Menu>
    );

153 154
    const iconGroup = (
      <span className={styles.iconGroup}>
afc163's avatar
afc163 committed
155 156 157
        <Dropdown overlay={menu} placement="bottomRight">
          <Icon type="ellipsis" />
        </Dropdown>
158 159 160
      </span>
    );

afc163's avatar
afc163 committed
161 162 163
    const salesExtra = (
      <div className={styles.salesExtraWrap}>
        <div className={styles.salesExtra}>
afc163's avatar
afc163 committed
164 165 166 167 168 169 170 171 172 173 174 175
          <a className={this.isActive('today')} onClick={() => this.selectDate('today')}>
            今日
          </a>
          <a className={this.isActive('week')} onClick={() => this.selectDate('week')}>
            本周
          </a>
          <a className={this.isActive('month')} onClick={() => this.selectDate('month')}>
            本月
          </a>
          <a className={this.isActive('year')} onClick={() => this.selectDate('year')}>
            全年
          </a>
afc163's avatar
afc163 committed
176 177 178 179 180 181
        </div>
        <RangePicker
          value={rangePickerValue}
          onChange={this.handleRangePickerChange}
          style={{ width: 256 }}
        />
182
      </div>
afc163's avatar
afc163 committed
183
    );
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

    const columns = [
      {
        title: '排名',
        dataIndex: 'index',
        key: 'index',
      },
      {
        title: '搜索关键词',
        dataIndex: 'keyword',
        key: 'keyword',
        render: text => <a href="/">{text}</a>,
      },
      {
        title: '用户数',
        dataIndex: 'count',
        key: 'count',
        sorter: (a, b) => a.count - b.count,
afc163's avatar
afc163 committed
202
        className: styles.alignRight,
203 204 205 206 207 208 209
      },
      {
        title: '周涨幅',
        dataIndex: 'range',
        key: 'range',
        sorter: (a, b) => a.range - b.range,
        render: (text, record) => (
偏右's avatar
偏右 committed
210
          <Trend flag={record.status === 1 ? 'down' : 'up'}>
陈帅's avatar
陈帅 committed
211 212 213 214
            <span style={{ marginRight: 4 }}>
              {text}
              %
            </span>
偏右's avatar
偏右 committed
215
          </Trend>
216
        ),
afc163's avatar
afc163 committed
217
        align: 'right',
218 219 220
      },
    ];

nikogu's avatar
nikogu committed
221 222
    const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);

223
    const CustomTab = ({ data, currentTabKey: currentKey }) => (
niko's avatar
niko committed
224
      <Row gutter={8} style={{ width: 138, margin: '8px 0' }}>
225 226 227 228
        <Col span={12}>
          <NumberInfo
            title={data.name}
            subTitle="转化率"
偏右's avatar
偏右 committed
229 230
            gap={2}
            total={`${data.cvr * 100}%`}
niko's avatar
niko committed
231
            theme={currentKey !== data.name && 'light'}
232 233 234 235
          />
        </Col>
        <Col span={12} style={{ paddingTop: 36 }}>
          <Pie
nikogu's avatar
nikogu committed
236
            animate={false}
niko's avatar
niko committed
237
            color={currentKey !== data.name && '#BDE4FF'}
238 239 240 241 242 243 244 245 246 247 248 249 250
            inner={0.55}
            tooltip={false}
            margin={[0, 0, 0, 0]}
            percent={data.cvr * 100}
            height={64}
          />
        </Col>
      </Row>
    );

    const topColResponsiveProps = {
      xs: 24,
      sm: 12,
niko's avatar
niko committed
251 252 253
      md: 12,
      lg: 12,
      xl: 6,
254 255 256 257
      style: { marginBottom: 24 },
    };

    return (
陈帅's avatar
陈帅 committed
258
      <Fragment>
259 260 261 262
        <Row gutter={24}>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
niko's avatar
niko committed
263
              title="总销售额"
niko's avatar
niko committed
264 265 266 267 268
              action={
                <Tooltip title="指标说明">
                  <Icon type="info-circle-o" />
                </Tooltip>
              }
afc163's avatar
afc163 committed
269
              total={() => <Yuan>126560</Yuan>}
afc163's avatar
afc163 committed
270
              footer={<Field label="日均销售额" value={`¥${numeral(12423).format('0,0')}`} />}
271 272
              contentHeight={46}
            >
afc163's avatar
afc163 committed
273
              <Trend flag="up" style={{ marginRight: 16 }}>
陈帅's avatar
陈帅 committed
274 275
                周同比
                <span className={styles.trendText}>12%</span>
afc163's avatar
afc163 committed
276 277
              </Trend>
              <Trend flag="down">
陈帅's avatar
陈帅 committed
278 279
                日环比
                <span className={styles.trendText}>11%</span>
afc163's avatar
afc163 committed
280
              </Trend>
281 282 283 284 285 286
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
              title="访问量"
niko's avatar
niko committed
287 288 289 290 291
              action={
                <Tooltip title="指标说明">
                  <Icon type="info-circle-o" />
                </Tooltip>
              }
292 293 294 295
              total={numeral(8846).format('0,0')}
              footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
              contentHeight={46}
            >
niko's avatar
niko committed
296
              <MiniArea color="#975FE4" data={visitData} />
297 298 299 300 301 302
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
              title="支付笔数"
niko's avatar
niko committed
303 304 305 306 307
              action={
                <Tooltip title="指标说明">
                  <Icon type="info-circle-o" />
                </Tooltip>
              }
308 309 310 311
              total={numeral(6560).format('0,0')}
              footer={<Field label="转化率" value="60%" />}
              contentHeight={46}
            >
niko's avatar
niko committed
312
              <MiniBar data={visitData} />
313 314 315 316 317
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
niko's avatar
niko committed
318
              title="运营活动效果"
niko's avatar
niko committed
319 320 321 322 323
              action={
                <Tooltip title="指标说明">
                  <Icon type="info-circle-o" />
                </Tooltip>
              }
324
              total="78%"
afc163's avatar
afc163 committed
325
              footer={
afc163's avatar
afc163 committed
326
                <div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
afc163's avatar
afc163 committed
327
                  <Trend flag="up" style={{ marginRight: 16 }}>
陈帅's avatar
陈帅 committed
328 329
                    周同比
                    <span className={styles.trendText}>12%</span>
afc163's avatar
afc163 committed
330 331
                  </Trend>
                  <Trend flag="down">
陈帅's avatar
陈帅 committed
332 333
                    日环比
                    <span className={styles.trendText}>11%</span>
afc163's avatar
afc163 committed
334
                  </Trend>
偏右's avatar
偏右 committed
335
                </div>
afc163's avatar
afc163 committed
336
              }
337 338
              contentHeight={46}
            >
niko's avatar
niko committed
339
              <MiniProgress percent={78} strokeWidth={8} target={80} color="#13C2C2" />
340 341 342 343
            </ChartCard>
          </Col>
        </Row>

niko's avatar
niko committed
344
        <Card loading={loading} bordered={false} bodyStyle={{ padding: 0 }}>
345
          <div className={styles.salesCard}>
afc163's avatar
afc163 committed
346
            <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}>
347
              <TabPane tab="销售额" key="sales">
niko's avatar
niko committed
348
                <Row>
nikogu's avatar
nikogu committed
349
                  <Col xl={16} lg={12} md={12} sm={24} xs={24}>
350
                    <div className={styles.salesBar}>
nikogu's avatar
nikogu committed
351
                      <Bar height={295} title="销售额趋势" data={salesData} />
352
                    </div>
353
                  </Col>
nikogu's avatar
nikogu committed
354
                  <Col xl={8} lg={12} md={12} sm={24} xs={24}>
355 356 357
                    <div className={styles.salesRank}>
                      <h4 className={styles.rankingTitle}>门店销售额排名</h4>
                      <ul className={styles.rankingList}>
niko's avatar
niko committed
358 359 360 361 362 363 364
                        {rankingListData.map((item, i) => (
                          <li key={item.title}>
                            <span className={i < 3 ? styles.active : ''}>{i + 1}</span>
                            <span>{item.title}</span>
                            <span>{numeral(item.total).format('0,0')}</span>
                          </li>
                        ))}
365 366
                      </ul>
                    </div>
367 368 369
                  </Col>
                </Row>
              </TabPane>
370
              <TabPane tab="访问量" key="views">
陈帅's avatar
陈帅 committed
371
                <Row>
372 373
                  <Col xl={16} lg={12} md={12} sm={24} xs={24}>
                    <div className={styles.salesBar}>
niko's avatar
niko committed
374
                      <Bar height={292} title="访问量趋势" data={salesData} />
375 376 377 378 379 380
                    </div>
                  </Col>
                  <Col xl={8} lg={12} md={12} sm={24} xs={24}>
                    <div className={styles.salesRank}>
                      <h4 className={styles.rankingTitle}>门店访问量排名</h4>
                      <ul className={styles.rankingList}>
niko's avatar
niko committed
381 382
                        {rankingListData.map((item, i) => (
                          <li key={item.title}>
383
                            <span className={i < 3 ? styles.active : ''}>{i + 1}</span>
niko's avatar
niko committed
384 385 386 387
                            <span>{item.title}</span>
                            <span>{numeral(item.total).format('0,0')}</span>
                          </li>
                        ))}
388 389 390 391
                      </ul>
                    </div>
                  </Col>
                </Row>
392 393 394 395 396 397
              </TabPane>
            </Tabs>
          </div>
        </Card>

        <Row gutter={24}>
398
          <Col xl={12} lg={24} md={24} sm={24} xs={24}>
399
            <Card
400
              loading={loading}
401 402 403 404 405 406 407 408
              bordered={false}
              title="线上热门搜索"
              extra={iconGroup}
              style={{ marginTop: 24 }}
            >
              <Row gutter={68}>
                <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
                  <NumberInfo
afc163's avatar
afc163 committed
409 410 411 412 413 414 415 416
                    subTitle={
                      <span>
                        搜索用户数
                        <Tooltip title="指标文案">
                          <Icon style={{ marginLeft: 8 }} type="info-circle-o" />
                        </Tooltip>
                      </span>
                    }
偏右's avatar
偏右 committed
417
                    gap={8}
418 419 420 421
                    total={numeral(12321).format('0,0')}
                    status="up"
                    subTotal={17.1}
                  />
niko's avatar
niko committed
422
                  <MiniArea line height={45} data={visitData2} />
423 424 425 426 427 428 429
                </Col>
                <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
                  <NumberInfo
                    subTitle="人均搜索次数"
                    total={2.7}
                    status="down"
                    subTotal={26.2}
偏右's avatar
偏右 committed
430
                    gap={8}
431
                  />
niko's avatar
niko committed
432
                  <MiniArea line height={45} data={visitData2} />
433 434 435 436
                </Col>
              </Row>
              <Table
                rowKey={record => record.index}
afc163's avatar
afc163 committed
437
                size="small"
438 439 440 441 442 443 444 445 446
                columns={columns}
                dataSource={searchData}
                pagination={{
                  style: { marginBottom: 0 },
                  pageSize: 5,
                }}
              />
            </Card>
          </Col>
447
          <Col xl={12} lg={24} md={24} sm={24} xs={24}>
448
            <Card
449
              loading={loading}
niko's avatar
niko committed
450
              className={styles.salesCard}
451 452
              bordered={false}
              title="销售额类别占比"
453
              bodyStyle={{ padding: 24 }}
niko's avatar
niko committed
454
              extra={
niko's avatar
niko committed
455 456 457 458 459 460 461 462 463 464
                <div className={styles.salesCardExtra}>
                  {iconGroup}
                  <div className={styles.salesTypeRadio}>
                    <Radio.Group value={salesType} onChange={this.handleChangeSalesType}>
                      <Radio.Button value="all">全部渠道</Radio.Button>
                      <Radio.Button value="online">线上</Radio.Button>
                      <Radio.Button value="offline">门店</Radio.Button>
                    </Radio.Group>
                  </div>
                </div>
niko's avatar
niko committed
465
              }
afc163's avatar
afc163 committed
466
              style={{ marginTop: 24, minHeight: 509 }}
467
            >
偏右's avatar
偏右 committed
468
              <h4 style={{ marginTop: 8, marginBottom: 32 }}>销售额</h4>
afc163's avatar
afc163 committed
469 470 471
              <Pie
                hasLegend
                subTitle="销售额"
陈帅's avatar
陈帅 committed
472
                total={() => <Yuan>{salesPieData.reduce((pre, now) => now.y + pre, 0)}</Yuan>}
afc163's avatar
afc163 committed
473
                data={salesPieData}
afc163's avatar
afc163 committed
474
                valueFormat={value => <Yuan>{value}</Yuan>}
afc163's avatar
afc163 committed
475 476 477
                height={248}
                lineWidth={4}
              />
478 479 480 481 482
            </Card>
          </Col>
        </Row>

        <Card
483
          loading={loading}
niko's avatar
niko committed
484
          className={styles.offlineCard}
485
          bordered={false}
niko's avatar
niko committed
486 487
          bodyStyle={{ padding: '0 0 32px 0' }}
          style={{ marginTop: 32 }}
488
        >
niko's avatar
niko committed
489 490 491 492 493 494 495 496 497 498 499 500
          <Tabs activeKey={activeKey} onChange={this.handleTabChange}>
            {offlineData.map(shop => (
              <TabPane tab={<CustomTab data={shop} currentTabKey={activeKey} />} key={shop.name}>
                <div style={{ padding: '0 24px' }}>
                  <TimelineChart
                    height={400}
                    data={offlineChartData}
                    titleMap={{ y1: '客流量', y2: '支付笔数' }}
                  />
                </div>
              </TabPane>
            ))}
501 502
          </Tabs>
        </Card>
陈帅's avatar
陈帅 committed
503
      </Fragment>
504 505
    );
  }
afc163's avatar
afc163 committed
506
}