| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- import React, { Component } 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 GridContent from '@/components/PageHeaderWrapper/GridContent';
- import Yuan from '@/utils/Yuan';
- import { getTimeDistance } from '@/utils/utils';
- import styles from './Analysis.less';
- const { TabPane } = Tabs;
- const { RangePicker } = DatePicker;
- const rankingListData = [];
- for (let i = 0; i < 7; i += 1) {
- rankingListData.push({
- title: `工专路 ${i} 号店`,
- total: 323234,
- });
- }
- @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,
- });
- }
- this.state = {
- salesType: 'all',
- currentTabKey: '',
- loading: true,
- rangePickerValue: getTimeDistance('year'),
- };
- }
- state = {
- salesType: 'all',
- currentTabKey: '',
- rangePickerValue: getTimeDistance('year'),
- };
- componentDidMount() {
- const { dispatch } = this.props;
- this.reqRef = requestAnimationFrame(() => {
- dispatch({
- type: 'chart/fetch',
- });
- setTimeout(() => {
- this.setState({
- loading: false,
- });
- }, 1000);
- });
- }
- componentWillUnmount() {
- const { dispatch } = this.props;
- dispatch({
- type: 'chart/clear',
- });
- cancelAnimationFrame(this.reqRef);
- }
- handleChangeSalesType = e => {
- this.setState({
- salesType: e.target.value,
- });
- };
- handleTabChange = key => {
- this.setState({
- currentTabKey: key,
- });
- };
- handleRangePickerChange = rangePickerValue => {
- const { dispatch } = this.props;
- this.setState({
- rangePickerValue,
- });
- dispatch({
- type: 'chart/fetchSalesData',
- });
- };
- selectDate = type => {
- const { dispatch } = this.props;
- this.setState({
- rangePickerValue: getTimeDistance(type),
- });
- dispatch({
- type: 'chart/fetchSalesData',
- });
- };
- 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;
- }
- return '';
- }
- render() {
- const { rangePickerValue, salesType, loading: propsLoding, currentTabKey } = this.state;
- const { chart, loading: stateLoading } = this.props;
- const {
- visitData,
- visitData2,
- salesData,
- searchData,
- offlineData,
- offlineChartData,
- salesTypeData,
- salesTypeDataOnline,
- salesTypeDataOffline,
- } = chart;
- const loading = propsLoding || stateLoading;
- let salesPieData;
- if (salesType === 'all') {
- salesPieData = salesTypeData;
- } else {
- salesPieData = salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
- }
- const menu = (
- <Menu>
- <Menu.Item>操作一</Menu.Item>
- <Menu.Item>操作二</Menu.Item>
- </Menu>
- );
- const iconGroup = (
- <span className={styles.iconGroup}>
- <Dropdown overlay={menu} placement="bottomRight">
- <Icon type="ellipsis" />
- </Dropdown>
- </span>
- );
- const salesExtra = (
- <div className={styles.salesExtraWrap}>
- <div className={styles.salesExtra}>
- <a className={this.isActive('today')} onClick={() => this.selectDate('today')}>
- <FormattedMessage id="app.analysis.all-day" defaultMessage="All Day" />
- </a>
- <a className={this.isActive('week')} onClick={() => this.selectDate('week')}>
- <FormattedMessage id="app.analysis.all-week" defaultMessage="All Week" />
- </a>
- <a className={this.isActive('month')} onClick={() => this.selectDate('month')}>
- <FormattedMessage id="app.analysis.all-month" defaultMessage="All Month" />
- </a>
- <a className={this.isActive('year')} onClick={() => this.selectDate('year')}>
- <FormattedMessage id="app.analysis.all-year" defaultMessage="All Year" />
- </a>
- </div>
- <RangePicker
- value={rangePickerValue}
- onChange={this.handleRangePickerChange}
- style={{ width: 256 }}
- />
- </div>
- );
- const columns = [
- {
- title: <FormattedMessage id="app.analysis.table.rank" defaultMessage="Rank" />,
- dataIndex: 'index',
- key: 'index',
- },
- {
- title: (
- <FormattedMessage
- id="app.analysis.table.search-keyword"
- defaultMessage="Search keyword"
- />
- ),
- dataIndex: 'keyword',
- key: 'keyword',
- render: text => <a href="/">{text}</a>,
- },
- {
- title: <FormattedMessage id="app.analysis.table.users" defaultMessage="Users" />,
- dataIndex: 'count',
- key: 'count',
- sorter: (a, b) => a.count - b.count,
- className: styles.alignRight,
- },
- {
- title: (
- <FormattedMessage id="app.analysis.table.weekly-range" defaultMessage="Weekly Range" />
- ),
- dataIndex: 'range',
- key: 'range',
- sorter: (a, b) => a.range - b.range,
- render: (text, record) => (
- <Trend flag={record.status === 1 ? 'down' : 'up'}>
- <span style={{ marginRight: 4 }}>{text}%</span>
- </Trend>
- ),
- align: 'right',
- },
- ];
- const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);
- const CustomTab = ({ data, currentTabKey: currentKey }) => (
- <Row gutter={8} style={{ width: 138, margin: '8px 0' }}>
- <Col span={12}>
- <NumberInfo
- title={data.name}
- subTitle={
- <FormattedMessage
- id="app.analysis.conversion-rate"
- defaultMessage="Conversion Rate"
- />
- }
- gap={2}
- total={`${data.cvr * 100}%`}
- theme={currentKey !== data.name && 'light'}
- />
- </Col>
- <Col span={12} style={{ paddingTop: 36 }}>
- <Pie
- animate={false}
- color={currentKey !== data.name && '#BDE4FF'}
- inner={0.55}
- tooltip={false}
- margin={[0, 0, 0, 0]}
- percent={data.cvr * 100}
- height={64}
- />
- </Col>
- </Row>
- );
- const topColResponsiveProps = {
- xs: 24,
- sm: 12,
- md: 12,
- lg: 12,
- xl: 6,
- style: { marginBottom: 24 },
- };
- return (
- <GridContent>
- <Row gutter={24}>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- title={
- <FormattedMessage id="app.analysis.total-sales" defaultMessage="Total Sales" />
- }
- action={
- <Tooltip
- title={
- <FormattedMessage id="app.analysis.introduce" defaultMessage="introduce" />
- }
- >
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- loading={loading}
- total={() => <Yuan>126560</Yuan>}
- footer={
- <Field
- label={
- <FormattedMessage id="app.analysis.day-sales" defaultMessage="Day Sales" />
- }
- value={`¥${numeral(12423).format('0,0')}`}
- />
- }
- contentHeight={46}
- >
- <Trend flag="up" style={{ marginRight: 16 }}>
- <FormattedMessage id="app.analysis.week" defaultMessage="Weekly Changes" />
- <span className={styles.trendText}>12%</span>
- </Trend>
- <Trend flag="down">
- <FormattedMessage id="app.analysis.day" defaultMessage="Daily Changes" />
- <span className={styles.trendText}>11%</span>
- </Trend>
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- loading={loading}
- title={<FormattedMessage id="app.analysis.visits" defaultMessage="visits" />}
- action={
- <Tooltip
- title={
- <FormattedMessage id="app.analysis.introduce" defaultMessage="introduce" />
- }
- >
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total={numeral(8846).format('0,0')}
- footer={
- <Field
- label={
- <FormattedMessage id="app.analysis.day-visits" defaultMessage="Day Visits" />
- }
- value={numeral(1234).format('0,0')}
- />
- }
- contentHeight={46}
- >
- <MiniArea color="#975FE4" data={visitData} />
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- loading={loading}
- title={<FormattedMessage id="app.analysis.payments" defaultMessage="Payments" />}
- action={
- <Tooltip
- title={
- <FormattedMessage id="app.analysis.introduce" defaultMessage="Introduce" />
- }
- >
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total={numeral(6560).format('0,0')}
- footer={
- <Field
- label={
- <FormattedMessage
- id="app.analysis.conversion-rate"
- defaultMessage="Conversion Rate"
- />
- }
- value="60%"
- />
- }
- contentHeight={46}
- >
- <MiniBar data={visitData} />
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- loading={loading}
- bordered={false}
- title={
- <FormattedMessage
- id="app.analysis.operational-effect"
- defaultMessage="Operational Effect"
- />
- }
- action={
- <Tooltip
- title={
- <FormattedMessage id="app.analysis.introduce" defaultMessage="introduce" />
- }
- >
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total="78%"
- footer={
- <div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
- <Trend flag="up" style={{ marginRight: 16 }}>
- <FormattedMessage id="app.analysis.week" defaultMessage="Weekly changes" />
- <span className={styles.trendText}>12%</span>
- </Trend>
- <Trend flag="down">
- <FormattedMessage id="app.analysis.day" defaultMessage="Weekly changes" />
- <span className={styles.trendText}>11%</span>
- </Trend>
- </div>
- }
- contentHeight={46}
- >
- <MiniProgress percent={78} strokeWidth={8} target={80} color="#13C2C2" />
- </ChartCard>
- </Col>
- </Row>
- <Card loading={loading} bordered={false} bodyStyle={{ padding: 0 }}>
- <div className={styles.salesCard}>
- <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}>
- <TabPane
- tab={<FormattedMessage id="app.analysis.sales" defaultMessage="Sales" />}
- key="sales"
- >
- <Row>
- <Col xl={16} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesBar}>
- <Bar
- height={295}
- title={
- <FormattedMessage
- id="app.analysis.sales-trend"
- defaultMessage="Sales Trend"
- />
- }
- data={salesData}
- />
- </div>
- </Col>
- <Col xl={8} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesRank}>
- <h4 className={styles.rankingTitle}>
- <FormattedMessage
- id="app.analysis.sales-ranking"
- defaultMessage="Sales Ranking"
- />
- </h4>
- <ul className={styles.rankingList}>
- {this.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>
- </TabPane>
- <TabPane
- tab={<FormattedMessage id="app.analysis.visits" defaultMessage="Visits" />}
- key="views"
- >
- <Row>
- <Col xl={16} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesBar}>
- <Bar
- height={292}
- title={
- <FormattedMessage
- id="app.analysis.visits-trend"
- defaultMessage="Visits Trend"
- />
- }
- data={salesData}
- />
- </div>
- </Col>
- <Col xl={8} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesRank}>
- <h4 className={styles.rankingTitle}>
- <FormattedMessage
- id="app.analysis.visits-ranking"
- defaultMessage="Visits Ranking"
- />
- </h4>
- <ul className={styles.rankingList}>
- {this.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>
- </TabPane>
- </Tabs>
- </div>
- </Card>
- <Row gutter={24}>
- <Col xl={12} lg={24} md={24} sm={24} xs={24}>
- <Card
- loading={loading}
- bordered={false}
- title={
- <FormattedMessage
- id="app.analysis.online-top-search"
- defaultMessage="Online Top Search"
- />
- }
- extra={iconGroup}
- style={{ marginTop: 24 }}
- >
- <Row gutter={68}>
- <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
- <NumberInfo
- subTitle={
- <span>
- <FormattedMessage
- id="app.analysis.search-users"
- defaultMessage="search users"
- />
- <Tooltip
- title={
- <FormattedMessage
- id="app.analysis.introduce"
- defaultMessage="introduce"
- />
- }
- >
- <Icon style={{ marginLeft: 8 }} type="info-circle-o" />
- </Tooltip>
- </span>
- }
- gap={8}
- total={numeral(12321).format('0,0')}
- status="up"
- subTotal={17.1}
- />
- <MiniArea line height={45} data={visitData2} />
- </Col>
- <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
- <NumberInfo
- subTitle={
- <FormattedMessage
- id="app.analysis.per-capita-search"
- defaultMessage="Per Capita Search"
- />
- }
- total={2.7}
- status="down"
- subTotal={26.2}
- gap={8}
- />
- <MiniArea line height={45} data={visitData2} />
- </Col>
- </Row>
- <Table
- rowKey={record => record.index}
- size="small"
- columns={columns}
- dataSource={searchData}
- pagination={{
- style: { marginBottom: 0 },
- pageSize: 5,
- }}
- />
- </Card>
- </Col>
- <Col xl={12} lg={24} md={24} sm={24} xs={24}>
- <Card
- loading={loading}
- className={styles.salesCard}
- bordered={false}
- title={
- <FormattedMessage
- id="app.analysis.the-proportion-of-sales"
- defaultMessage="The Proportion of Sales"
- />
- }
- bodyStyle={{ padding: 24 }}
- extra={
- <div className={styles.salesCardExtra}>
- {iconGroup}
- <div className={styles.salesTypeRadio}>
- <Radio.Group value={salesType} onChange={this.handleChangeSalesType}>
- <Radio.Button value="all">
- <FormattedMessage id="app.analysis.channel.all" defaultMessage="ALL" />
- </Radio.Button>
- <Radio.Button value="online">
- <FormattedMessage
- id="app.analysis.channel.online"
- defaultMessage="Online"
- />
- </Radio.Button>
- <Radio.Button value="stores">
- <FormattedMessage
- id="app.analysis.channel.stores"
- defaultMessage="Stores"
- />
- </Radio.Button>
- </Radio.Group>
- </div>
- </div>
- }
- style={{ marginTop: 24, minHeight: 509 }}
- >
- <h4 style={{ marginTop: 8, marginBottom: 32 }}>
- <FormattedMessage id="app.analysis.sales" defaultMessage="Sales" />
- </h4>
- <Pie
- hasLegend
- subTitle={<FormattedMessage id="app.analysis.sales" defaultMessage="Sales" />}
- total={() => <Yuan>{salesPieData.reduce((pre, now) => now.y + pre, 0)}</Yuan>}
- data={salesPieData}
- valueFormat={value => <Yuan>{value}</Yuan>}
- height={248}
- lineWidth={4}
- />
- </Card>
- </Col>
- </Row>
- <Card
- loading={loading}
- className={styles.offlineCard}
- bordered={false}
- bodyStyle={{ padding: '0 0 32px 0' }}
- style={{ marginTop: 32 }}
- >
- <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: formatMessage({ id: 'app.analysis.traffic' }),
- y2: formatMessage({ id: 'app.analysis.payments' }),
- }}
- />
- </div>
- </TabPane>
- ))}
- </Tabs>
- </Card>
- </GridContent>
- );
- }
- }
- export default Analysis;
|