Analysis.js 14.6 KB
Newer Older
1 2 3 4
import React, { Component } from 'react';
import { connect } from 'dva';
import { Row, Col, Icon, Card, Tabs, Table, Radio, DatePicker, Tooltip } from 'antd';
import numeral from 'numeral';
偏右's avatar
偏右 committed
5
import {
偏右's avatar
偏右 committed
6
  ChartCard, yuan, MiniArea, MiniBar, MiniProgress, Field, Bar, Pie, TimelineChart,
偏右's avatar
偏右 committed
7 8
} from '../../components/Charts';
import Trend from '../../components/Trend';
偏右's avatar
偏右 committed
9
import NumberInfo from '../../components/NumberInfo';
10 11 12 13
import { getTimeDistance } from '../../utils/utils';

import styles from './Analysis.less';

afc163's avatar
afc163 committed
14
const { TabPane } = Tabs;
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
const { RangePicker } = DatePicker;

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

@connect(state => ({
  chart: state.chart,
}))
export default class Analysis extends Component {
  state = {
    salesType: 'all',
    currentTabKey: '',
    rangePickerValue: [],
  }

  componentDidMount() {
    this.props.dispatch({
      type: 'chart/fetch',
    });
  }

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

  handleChangeSalesType = (e) => {
    this.setState({
      salesType: e.target.value,
    });
  }

  handleTabChange = (key) => {
    this.setState({
      currentTabKey: key,
    });
  }

  handleRangePickerChange = (rangePickerValue) => {
    this.setState({
      rangePickerValue,
    });
nikogu's avatar
nikogu committed
64 65 66 67

    this.props.dispatch({
      type: 'chart/fetchSalesData',
    });
68 69 70 71 72 73 74 75 76 77 78 79
  }

  selectDate = (type) => {
    this.setState({
      rangePickerValue: getTimeDistance(type),
    });

    this.props.dispatch({
      type: 'chart/fetchSalesData',
    });
  }

afc163's avatar
afc163 committed
80 81 82 83 84 85 86 87 88 89 90
  isActive(type) {
    const { rangePickerValue } = this.state;
    const value = getTimeDistance(type);
    if (!rangePickerValue[0] || !rangePickerValue[1]) {
      return;
    }
    if (rangePickerValue[0].isSame(value[0], 'day') && rangePickerValue[1].isSame(value[1], 'day')) {
      return styles.currentDate;
    }
  }

91 92 93 94 95
  render() {
    const { rangePickerValue, salesType, currentTabKey } = this.state;
    const { chart } = this.props;
    const {
      visitData,
afc163's avatar
afc163 committed
96
      visitData2,
97 98 99 100 101 102 103
      salesData,
      searchData,
      offlineData,
      offlineChartData,
      salesTypeData,
      salesTypeDataOnline,
      salesTypeDataOffline,
afc163's avatar
afc163 committed
104
    } = chart;
105 106 107 108 109 110 111 112 113 114 115 116

    const salesPieData = salesType === 'all' ?
      salesTypeData
      :
      (salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline);

    const iconGroup = (
      <span className={styles.iconGroup}>
        <Icon type="camera-o" /><Icon type="export" /><Icon type="ellipsis" />
      </span>
    );

afc163's avatar
afc163 committed
117 118 119
    const salesExtra = (
      <div className={styles.salesExtraWrap}>
        <div className={styles.salesExtra}>
afc163's avatar
afc163 committed
120 121 122 123 124 125 126 127 128 129 130 131
          <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
132 133 134 135 136 137
        </div>
        <RangePicker
          value={rangePickerValue}
          onChange={this.handleRangePickerChange}
          style={{ width: 256 }}
        />
138
      </div>
afc163's avatar
afc163 committed
139
    );
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

    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
158
        className: styles.alignRight,
159 160 161 162 163 164 165
      },
      {
        title: '周涨幅',
        dataIndex: 'range',
        key: 'range',
        sorter: (a, b) => a.range - b.range,
        render: (text, record) => (
偏右's avatar
偏右 committed
166 167 168
          <Trend flag={record.status === 1 ? 'down' : 'up'}>
            <span style={{ marginRight: 4 }}>{text}%</span>
          </Trend>
169
        ),
afc163's avatar
afc163 committed
170
        className: styles.alignRight,
171 172 173
      },
    ];

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

176
    const CustomTab = ({ data, currentTabKey: currentKey }) => (
niko's avatar
niko committed
177
      <Row gutter={8} style={{ width: 138, margin: '8px 0' }}>
178 179 180 181
        <Col span={12}>
          <NumberInfo
            title={data.name}
            subTitle="转化率"
偏右's avatar
偏右 committed
182 183
            gap={2}
            total={`${data.cvr * 100}%`}
184 185 186 187 188
            theme={(currentKey !== data.name) && 'light'}
          />
        </Col>
        <Col span={12} style={{ paddingTop: 36 }}>
          <Pie
nikogu's avatar
nikogu committed
189
            animate={false}
afc163's avatar
Fix pie  
afc163 committed
190
            color={(currentKey !== data.name) && '#BDE4FF'}
191 192 193 194 195 196 197 198 199 200 201 202 203
            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
204 205 206
      md: 12,
      lg: 12,
      xl: 6,
207 208 209 210 211 212 213 214 215
      style: { marginBottom: 24 },
    };

    return (
      <div>
        <Row gutter={24}>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
niko's avatar
niko committed
216
              title="总销售额"
afc163's avatar
afc163 committed
217
              action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
218
              total={yuan(126560)}
afc163's avatar
afc163 committed
219
              footer={<Field label="日均销售额" value={`¥${numeral(12423).format('0,0')}`} />}
220 221
              contentHeight={46}
            >
偏右's avatar
偏右 committed
222 223 224 225 226 227 228 229
              <span>
                周同比
                <Trend flag="up" className={styles.trend}>12%</Trend>
              </span>
              <span style={{ marginLeft: 16 }}>
                日环比
                <Trend flag="down" className={styles.trend}>11%</Trend>
              </span>
230 231 232 233 234 235
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
              title="访问量"
afc163's avatar
afc163 committed
236
              action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
237 238 239 240 241
              total={numeral(8846).format('0,0')}
              footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
              contentHeight={46}
            >
              <MiniArea
niko's avatar
niko committed
242
                color="#975FE4"
243 244 245 246 247 248 249 250 251
                height={46}
                data={visitData}
              />
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
              title="支付笔数"
afc163's avatar
afc163 committed
252
              action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
253 254 255 256 257 258 259 260 261 262 263 264 265
              total={numeral(6560).format('0,0')}
              footer={<Field label="转化率" value="60%" />}
              contentHeight={46}
            >
              <MiniBar
                height={46}
                data={visitData}
              />
            </ChartCard>
          </Col>
          <Col {...topColResponsiveProps}>
            <ChartCard
              bordered={false}
niko's avatar
niko committed
266
              title="运营活动效果"
afc163's avatar
afc163 committed
267
              action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
268
              total="78%"
afc163's avatar
afc163 committed
269
              footer={
偏右's avatar
偏右 committed
270 271 272 273 274 275 276 277 278 279
                <div>
                  <span>
                    周同比
                    <Trend flag="up" className={styles.trend}>12%</Trend>
                  </span>
                  <span style={{ marginLeft: 16 }}>
                    日环比
                    <Trend flag="down" className={styles.trend}>11%</Trend>
                  </span>
                </div>
afc163's avatar
afc163 committed
280
              }
281 282
              contentHeight={46}
            >
niko's avatar
niko committed
283
              <MiniProgress percent={78} strokeWidth={8} target={80} color="#13C2C2" />
284 285 286 287 288 289 290 291 292
            </ChartCard>
          </Col>
        </Row>

        <Card
          bordered={false}
          bodyStyle={{ padding: 0 }}
        >
          <div className={styles.salesCard}>
afc163's avatar
afc163 committed
293
            <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}>
294
              <TabPane tab="销售额" key="sales">
niko's avatar
niko committed
295
                <Row>
nikogu's avatar
nikogu committed
296
                  <Col xl={16} lg={12} md={12} sm={24} xs={24}>
297 298
                    <div className={styles.salesBar}>
                      <Bar
niko's avatar
niko committed
299
                        height={295}
300 301 302 303
                        title="销售额趋势"
                        data={salesData}
                      />
                    </div>
304
                  </Col>
nikogu's avatar
nikogu committed
305
                  <Col xl={8} lg={12} md={12} sm={24} xs={24}>
306 307 308 309 310 311 312 313 314 315 316 317 318 319
                    <div className={styles.salesRank}>
                      <h4 className={styles.rankingTitle}>门店销售额排名</h4>
                      <ul className={styles.rankingList}>
                        {
                          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>
                          ))
                        }
                      </ul>
                    </div>
320 321 322
                  </Col>
                </Row>
              </TabPane>
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
              <TabPane tab="访问量" key="views">
                <Row gutter={72}>
                  <Col xl={16} lg={12} md={12} sm={24} xs={24}>
                    <div className={styles.salesBar}>
                      <Bar
                        height={292}
                        title="访问量趋势"
                        data={salesData}
                      />
                    </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}>
                        {
                          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>
                          ))
                        }
                      </ul>
                    </div>
                  </Col>
                </Row>
351 352 353 354 355 356
              </TabPane>
            </Tabs>
          </div>
        </Card>

        <Row gutter={24}>
nikogu's avatar
nikogu committed
357
          <Col lg={12} sm={24} xs={24}>
358 359 360 361 362 363 364 365 366
            <Card
              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
367 368 369 370 371 372 373 374
                    subTitle={
                      <span>
                        搜索用户数
                        <Tooltip title="指标文案">
                          <Icon style={{ marginLeft: 8 }} type="info-circle-o" />
                        </Tooltip>
                      </span>
                    }
偏右's avatar
偏右 committed
375
                    gap={8}
376 377 378 379 380 381 382
                    total={numeral(12321).format('0,0')}
                    status="up"
                    subTotal={17.1}
                  />
                  <MiniArea
                    line
                    height={45}
afc163's avatar
afc163 committed
383
                    data={visitData2}
384 385 386 387 388 389 390 391
                  />
                </Col>
                <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
                  <NumberInfo
                    subTitle="人均搜索次数"
                    total={2.7}
                    status="down"
                    subTotal={26.2}
偏右's avatar
偏右 committed
392
                    gap={8}
393 394 395 396
                  />
                  <MiniArea
                    line
                    height={45}
afc163's avatar
afc163 committed
397
                    data={visitData2}
398 399 400 401 402
                  />
                </Col>
              </Row>
              <Table
                rowKey={record => record.index}
afc163's avatar
afc163 committed
403
                size="small"
404 405 406 407 408 409 410 411 412 413 414
                columns={columns}
                dataSource={searchData}
                pagination={{
                  style: { marginBottom: 0 },
                  showSizeChanger: true,
                  showQuickJumper: true,
                  pageSize: 5,
                }}
              />
            </Card>
          </Col>
nikogu's avatar
nikogu committed
415
          <Col lg={12} sm={24} xs={24}>
416
            <Card
niko's avatar
niko committed
417
              className={styles.salesCard}
418 419
              bordered={false}
              title="销售额类别占比"
niko's avatar
niko committed
420 421 422 423 424 425 426 427 428 429 430 431
              extra={(
                <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>
              )}
432 433
              style={{ marginTop: 24 }}
            >
afc163's avatar
afc163 committed
434
              <div style={{ marginTop: 8, marginBottom: 77 }}>
435 436 437 438 439 440 441
                <Pie
                  hasLegend
                  title="销售额"
                  subTitle="销售额"
                  total={yuan(salesPieData.reduce((pre, now) => now.y + pre, 0))}
                  data={salesPieData}
                  valueFormat={val => yuan(val)}
afc163's avatar
Fix pie  
afc163 committed
442 443
                  height={268}
                  lineWidth={4}
444 445 446 447 448 449 450
                />
              </div>
            </Card>
          </Col>
        </Row>

        <Card
niko's avatar
niko committed
451
          className={styles.offlineCard}
452
          bordered={false}
niko's avatar
niko committed
453 454
          bodyStyle={{ padding: '0 0 32px 0' }}
          style={{ marginTop: 32 }}
455 456
        >
          <Tabs
nikogu's avatar
nikogu committed
457
            activeKey={activeKey}
458 459 460 461 462
            onChange={this.handleTabChange}
          >
            {
              offlineData.map(shop => (
                <TabPane
nikogu's avatar
nikogu committed
463
                  tab={<CustomTab data={shop} currentTabKey={activeKey} />}
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
                  key={shop.name}
                >
                  <div style={{ padding: '0 24px' }}>
                    <TimelineChart
                      data={offlineChartData}
                      titleMap={{ y1: '客流量', y2: '支付笔数' }}
                    />
                  </div>
                </TabPane>)
              )
            }
          </Tabs>
        </Card>
      </div>
    );
  }
afc163's avatar
afc163 committed
480
}