|
@@ -1,7 +1,7 @@
|
|
|
import React, { useState, useEffect } from 'react'
|
|
import React, { useState, useEffect } from 'react'
|
|
|
import ProTable from '@ant-design/pro-table'
|
|
import ProTable from '@ant-design/pro-table'
|
|
|
import { connect, useModel } from 'umi'
|
|
import { connect, useModel } from 'umi'
|
|
|
-import { Row, Col, Divider, Select } from 'antd'
|
|
|
|
|
|
|
+import { Row, Col, Select, Card, Skeleton } from 'antd'
|
|
|
import { AddressBook, City, Finance, Comment } from '@icon-park/react'
|
|
import { AddressBook, City, Finance, Comment } from '@icon-park/react'
|
|
|
import { DualAxes } from '@ant-design/charts'
|
|
import { DualAxes } from '@ant-design/charts'
|
|
|
import consts from '@/consts'
|
|
import consts from '@/consts'
|
|
@@ -14,6 +14,7 @@ import { cyclicalOp } from './consts'
|
|
|
const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
|
const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
|
|
const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
|
|
|
const [state, setState] = useState({
|
|
const [state, setState] = useState({
|
|
|
|
|
+ loading: true,
|
|
|
visible: false,
|
|
visible: false,
|
|
|
reminderData: [],
|
|
reminderData: [],
|
|
|
leaderboard: [],
|
|
leaderboard: [],
|
|
@@ -50,17 +51,20 @@ const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
|
}
|
|
}
|
|
|
} = await queryDashboard(payload)
|
|
} = await queryDashboard(payload)
|
|
|
if (code === consts.RET_CODE.SUCCESS) {
|
|
if (code === consts.RET_CODE.SUCCESS) {
|
|
|
- setState({
|
|
|
|
|
- ...state,
|
|
|
|
|
- reminderData,
|
|
|
|
|
- leaderboard,
|
|
|
|
|
- clientChainRatio,
|
|
|
|
|
- customerChainRatio,
|
|
|
|
|
- businessChainRatio,
|
|
|
|
|
- serviceLogChainRatio,
|
|
|
|
|
- aggregation,
|
|
|
|
|
- businessChar
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ setState({
|
|
|
|
|
+ ...state,
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ reminderData,
|
|
|
|
|
+ leaderboard,
|
|
|
|
|
+ clientChainRatio,
|
|
|
|
|
+ customerChainRatio,
|
|
|
|
|
+ businessChainRatio,
|
|
|
|
|
+ serviceLogChainRatio,
|
|
|
|
|
+ aggregation,
|
|
|
|
|
+ businessChar
|
|
|
|
|
+ })
|
|
|
|
|
+ }, 1000)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -201,11 +205,17 @@ const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
|
<div className="shadow-card">
|
|
<div className="shadow-card">
|
|
|
<ProTable
|
|
<ProTable
|
|
|
bordered
|
|
bordered
|
|
|
|
|
+ loading={state.loading}
|
|
|
rowKey={record => record.type + Date.now()}
|
|
rowKey={record => record.type + Date.now()}
|
|
|
columns={columns}
|
|
columns={columns}
|
|
|
dataSource={state.reminderData}
|
|
dataSource={state.reminderData}
|
|
|
search={false}
|
|
search={false}
|
|
|
toolBarRender={false}
|
|
toolBarRender={false}
|
|
|
|
|
+ tableRender={(_, dom) => (
|
|
|
|
|
+ <Card bodyStyle={{ padding: state.loading ? '24px' : 0 }} loading={state.loading}>
|
|
|
|
|
+ {dom}
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ )}
|
|
|
pagination={false}
|
|
pagination={false}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
@@ -226,128 +236,162 @@ const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="mt-4">
|
|
<div className="mt-4">
|
|
|
- <div className="p-4 bg-white border rounded-lg shadow-card">
|
|
|
|
|
- <Row gutter={16}>
|
|
|
|
|
- <Col className="gutter-row" span={6}>
|
|
|
|
|
- <div className="p-4 bg-white border rounded-lg">
|
|
|
|
|
|
|
+ <Row gutter={16}>
|
|
|
|
|
+ <Col className="gutter-row" span={6}>
|
|
|
|
|
+ <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0">
|
|
|
|
|
+ {state.loading ? (
|
|
|
|
|
+ <Skeleton active avatar />
|
|
|
|
|
+ ) : (
|
|
|
<Row>
|
|
<Row>
|
|
|
<Col span={6}>
|
|
<Col span={6}>
|
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
- {/* <span className="absolute top-9px left-9px"> */}
|
|
|
|
|
<City size="26" fill="#fff" />
|
|
<City size="26" fill="#fff" />
|
|
|
- {/* </span> */}
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</Col>
|
|
</Col>
|
|
|
- <Col span={9}>
|
|
|
|
|
- <h3 className="text-2xl">{state.customerChainRatio.count}</h3>
|
|
|
|
|
- <span>新增客户</span>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col span={9} className="text-right">
|
|
|
|
|
- <h3 className="text-xl text-green-500">
|
|
|
|
|
- {state.customerChainRatio.percentage}
|
|
|
|
|
- </h3>
|
|
|
|
|
- <span>
|
|
|
|
|
- {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <Col span={18} flex="wrap">
|
|
|
|
|
+ <div className="flex items-center justify-between ">
|
|
|
|
|
+ <div className="text-2xl">{state.customerChainRatio.count}</div>
|
|
|
|
|
+ <span className="text-xl text-green-500">
|
|
|
|
|
+ {state.customerChainRatio.percentage}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <div>新增客户</div>
|
|
|
|
|
+ <span>
|
|
|
|
|
+ {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
- </div>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col className="gutter-row" span={6}>
|
|
|
|
|
- <div className="p-4 bg-white border rounded-lg shadow-card">
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ <Col className="gutter-row" span={6}>
|
|
|
|
|
+ <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0">
|
|
|
|
|
+ {state.loading ? (
|
|
|
|
|
+ <Skeleton active avatar />
|
|
|
|
|
+ ) : (
|
|
|
<Row>
|
|
<Row>
|
|
|
<Col span={6}>
|
|
<Col span={6}>
|
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
<AddressBook size="26" fill="#fff" />
|
|
<AddressBook size="26" fill="#fff" />
|
|
|
</div>
|
|
</div>
|
|
|
</Col>
|
|
</Col>
|
|
|
- <Col span={9}>
|
|
|
|
|
- <h3 className="text-2xl">{state.clientChainRatio.count}</h3>
|
|
|
|
|
- <span>新增联系人</span>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col span={9} className="text-right">
|
|
|
|
|
- <h3 className="text-xl text-red-500">{state.clientChainRatio.percentage}</h3>
|
|
|
|
|
- <span>
|
|
|
|
|
- {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <Col span={18} flex="wrap">
|
|
|
|
|
+ <div className="flex items-center justify-between ">
|
|
|
|
|
+ <div className="text-2xl">{state.clientChainRatio.count}</div>
|
|
|
|
|
+ <span className="text-xl text-green-500">
|
|
|
|
|
+ {state.clientChainRatio.percentage}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <div>新增联系人</div>
|
|
|
|
|
+ <span>
|
|
|
|
|
+ {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
- </div>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col className="gutter-row" span={6}>
|
|
|
|
|
- <div className="p-4 bg-white border rounded-lg">
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ <Col className="gutter-row" span={6}>
|
|
|
|
|
+ <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0">
|
|
|
|
|
+ {state.loading ? (
|
|
|
|
|
+ <Skeleton active avatar />
|
|
|
|
|
+ ) : (
|
|
|
<Row>
|
|
<Row>
|
|
|
<Col span={6}>
|
|
<Col span={6}>
|
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
<Finance size="26" fill="#fff" />
|
|
<Finance size="26" fill="#fff" />
|
|
|
</div>
|
|
</div>
|
|
|
</Col>
|
|
</Col>
|
|
|
- <Col span={9}>
|
|
|
|
|
- <h3 className="text-2xl">{state.businessChainRatio.count}</h3>
|
|
|
|
|
- <span>新增商机</span>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col span={9} className="text-right">
|
|
|
|
|
- <h3 className="text-xl text-green-500">
|
|
|
|
|
- {state.businessChainRatio.percentage}
|
|
|
|
|
- </h3>
|
|
|
|
|
- <span>
|
|
|
|
|
- {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <Col span={18} flex="wrap">
|
|
|
|
|
+ <div className="flex items-center justify-between ">
|
|
|
|
|
+ <div className="text-2xl">{state.businessChainRatio.count}</div>
|
|
|
|
|
+ <span className="text-xl text-green-500">
|
|
|
|
|
+ {state.businessChainRatio.percentage}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <div>新增商机</div>
|
|
|
|
|
+ <span>
|
|
|
|
|
+ {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
- </div>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col className="gutter-row" span={6}>
|
|
|
|
|
- <div className="p-4 bg-white border rounded-lg">
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ <Col className="gutter-row" span={6}>
|
|
|
|
|
+ <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0">
|
|
|
|
|
+ {state.loading ? (
|
|
|
|
|
+ <Skeleton active avatar />
|
|
|
|
|
+ ) : (
|
|
|
<Row>
|
|
<Row>
|
|
|
<Col span={6}>
|
|
<Col span={6}>
|
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
<div className="w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
<Comment size="26" fill="#fff" />
|
|
<Comment size="26" fill="#fff" />
|
|
|
</div>
|
|
</div>
|
|
|
</Col>
|
|
</Col>
|
|
|
- <Col span={9}>
|
|
|
|
|
- <h3 className="text-2xl">{state.serviceLogChainRatio.count}</h3>
|
|
|
|
|
- <span>服务记录</span>
|
|
|
|
|
- </Col>
|
|
|
|
|
- <Col span={9} className="text-right">
|
|
|
|
|
- <h3 className="text-xl text-green-500">
|
|
|
|
|
- {state.serviceLogChainRatio.percentage}
|
|
|
|
|
- </h3>
|
|
|
|
|
- <span>
|
|
|
|
|
- {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <Col span={18} flex="wrap">
|
|
|
|
|
+ <div className="flex items-center justify-between ">
|
|
|
|
|
+ <div className="text-2xl">{state.serviceLogChainRatio.count}</div>
|
|
|
|
|
+ <span className="text-xl text-green-500">
|
|
|
|
|
+ {state.serviceLogChainRatio.percentage}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <div>服务记录</div>
|
|
|
|
|
+ <span>
|
|
|
|
|
+ {cyclicalOp.find(item => item.value === state.params.cyclical)?.title}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
- </div>
|
|
|
|
|
- </Col>
|
|
|
|
|
- </Row>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ </Row>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="mt-4">
|
|
<div className="mt-4">
|
|
|
<Row gutter={16}>
|
|
<Row gutter={16}>
|
|
|
<Col className="gutter-row" span={16}>
|
|
<Col className="gutter-row" span={16}>
|
|
|
- <div className="p-4 bg-white border rounded-lg shadow-card">
|
|
|
|
|
- <div className="relative">
|
|
|
|
|
- <span>销售漏斗</span>
|
|
|
|
|
- <div className="absolute right-1 top-0">
|
|
|
|
|
- {groupList ? (
|
|
|
|
|
- <Select
|
|
|
|
|
- defaultValue={groupList[0]?.value}
|
|
|
|
|
- options={groupList}
|
|
|
|
|
- onChange={e =>
|
|
|
|
|
- setState({ ...state, params: { ...state.params, businessGroupId: e } })
|
|
|
|
|
- }
|
|
|
|
|
- />
|
|
|
|
|
- ) : null}
|
|
|
|
|
|
|
+ <Card
|
|
|
|
|
+ loading={state.loading}
|
|
|
|
|
+ bodyStyle={{ minHeight: '230px' }}
|
|
|
|
|
+ title={
|
|
|
|
|
+ <div className="relative">
|
|
|
|
|
+ <span>销售漏斗</span>
|
|
|
|
|
+ <div className="absolute right-1 top-0">
|
|
|
|
|
+ {groupList ? (
|
|
|
|
|
+ <Select
|
|
|
|
|
+ defaultValue={groupList[0]?.value}
|
|
|
|
|
+ options={groupList}
|
|
|
|
|
+ onChange={e =>
|
|
|
|
|
+ setState({ ...state, params: { ...state.params, businessGroupId: e } })
|
|
|
|
|
+ }
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- <Divider />
|
|
|
|
|
|
|
+ }>
|
|
|
{char && thread && <DualAxes {...config} />}
|
|
{char && thread && <DualAxes {...config} />}
|
|
|
- </div>
|
|
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ {/* <div className="p-4 bg-white border rounded-lg shadow-card"> */}
|
|
|
|
|
+ {/* <Divider /> */}
|
|
|
|
|
+ {/* </div> */}
|
|
|
</Col>
|
|
</Col>
|
|
|
<Col className="gutter-row" span={8}>
|
|
<Col className="gutter-row" span={8}>
|
|
|
- <div className="bg-white border rounded-lg shadow-card">
|
|
|
|
|
- <h4 className="px-12px py-16px border-b-1">数据汇总</h4>
|
|
|
|
|
|
|
+ <Card
|
|
|
|
|
+ headStyle={{ padding: '0 12px' }}
|
|
|
|
|
+ title="数据汇总"
|
|
|
|
|
+ bodyStyle={{ padding: state.loading ? '24px' : 0 }}
|
|
|
|
|
+ loading={state.loading}>
|
|
|
<ul>
|
|
<ul>
|
|
|
<li className="px-12px py-16px border-b-1">
|
|
<li className="px-12px py-16px border-b-1">
|
|
|
新增客户<b className="px-1">{state.aggregation.customerCount}</b>个,服务
|
|
新增客户<b className="px-1">{state.aggregation.customerCount}</b>个,服务
|
|
@@ -364,16 +408,16 @@ const Dashboard = ({ dispatch, groupList = [] }) => {
|
|
|
条,赢单<b className="px-1">{state.aggregation.businessWinCount}</b>个
|
|
条,赢单<b className="px-1">{state.aggregation.businessWinCount}</b>个
|
|
|
</li>
|
|
</li>
|
|
|
</ul>
|
|
</ul>
|
|
|
- </div>
|
|
|
|
|
|
|
+ </Card>
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="mt-4">
|
|
<div className="mt-4">
|
|
|
<Row gutter={16}>
|
|
<Row gutter={16}>
|
|
|
<Col className="gutter-row" span={12}>
|
|
<Col className="gutter-row" span={12}>
|
|
|
- <div className="bg-white border rounded-lg shadow-card">
|
|
|
|
|
- <LeaderBoard dataList={state.leaderboard} />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {/* <div className="bg-white border rounded-lg shadow-card"> */}
|
|
|
|
|
+ <LeaderBoard dataList={state.leaderboard} loading={state.loading} />
|
|
|
|
|
+ {/* </div> */}
|
|
|
</Col>
|
|
</Col>
|
|
|
</Row>
|
|
</Row>
|
|
|
</div>
|
|
</div>
|