|
|
@@ -1,520 +0,0 @@
|
|
|
-import React, { useState, useEffect, useRef } from 'react'
|
|
|
-import ProTable from '@ant-design/pro-table'
|
|
|
-import { Row, Col, Select, Card, Skeleton } from 'antd'
|
|
|
-import { AddressBook, City, Finance, Comment } from '@icon-park/react'
|
|
|
-import consts from '@/consts'
|
|
|
-import { useModal } from '@/components/Modal'
|
|
|
-import { queryDashboard } from '@/services/dashboard'
|
|
|
-import ReminderList from './components/ReminderList'
|
|
|
-import LeaderBoard from './components/LeaderBoard'
|
|
|
-import SoftLeaderboard from './components/SoftLeaderboard'
|
|
|
-import { cardTypeMap, cyclicalOp } from './consts'
|
|
|
-import BusinessChar from './components/BusinessChar'
|
|
|
-import RatioPanels from './components/RatioPanels'
|
|
|
-import PermSelect, { PermDataTypeEunm } from '@/pages/Customer/Company/components/PermSelect'
|
|
|
-import { getAuthCache, getPermAuthCache, setAuthCache } from '@/utils/auth'
|
|
|
-import { WORKBENCH_CYCLICAL_KEY } from '@/utils/cache/cacheEnum'
|
|
|
-import styles from './index.less'
|
|
|
-
|
|
|
-const Dashboard = () => {
|
|
|
- const timer = useRef(null)
|
|
|
- const [defaultPermAuthCache, defaultStaffIds] = getPermAuthCache(PermDataTypeEunm.WORKBENCH)
|
|
|
-
|
|
|
- const cyclicalAuthCache = getAuthCache(WORKBENCH_CYCLICAL_KEY)
|
|
|
- let defaultCyclicalValue = 'week'
|
|
|
- if (cyclicalAuthCache) {
|
|
|
- defaultCyclicalValue = cyclicalAuthCache
|
|
|
- }
|
|
|
- const [state, setState] = useState({
|
|
|
- disabled: true,
|
|
|
- loading: true,
|
|
|
- staffIds: null,
|
|
|
- immediate: false,
|
|
|
- visible: false,
|
|
|
- reminderData: [],
|
|
|
- leaderboard: [],
|
|
|
- productLeaderBoard: [],
|
|
|
- clientChainRatio: {},
|
|
|
- customerChainRatio: {},
|
|
|
- businessChainRatio: {},
|
|
|
- serviceLogChainRatio: {},
|
|
|
- aggregation: {},
|
|
|
- businessChar: {},
|
|
|
- params: {
|
|
|
- businessGroupId: '',
|
|
|
- dataPermission: defaultPermAuthCache,
|
|
|
- staffIds: defaultStaffIds,
|
|
|
- cyclical: defaultCyclicalValue
|
|
|
- }
|
|
|
- })
|
|
|
- const { toggleModal, setModalProps } = useModal()
|
|
|
- // 展示服务弹窗详情
|
|
|
- const handleReminderList = (dataType, reminderCyclical) => {
|
|
|
- setModalProps({
|
|
|
- width: '90vw',
|
|
|
- modalRender: () => <ReminderList dataType={dataType} reminderCyclical={reminderCyclical} />,
|
|
|
- onCancel: () => toggleModal()
|
|
|
- })
|
|
|
- toggleModal()
|
|
|
- }
|
|
|
- // 展示卡片详情
|
|
|
- const handleRatioCard = dataType => {
|
|
|
- setModalProps({
|
|
|
- width: '90vw',
|
|
|
- modalRender: () => (
|
|
|
- <RatioPanels
|
|
|
- dataType={dataType}
|
|
|
- staffIds={state.params.staffIds}
|
|
|
- retioCyclical={state.params.cyclical}
|
|
|
- dataPermission={state.params.dataPermission}
|
|
|
- />
|
|
|
- ),
|
|
|
- onCancel: () => toggleModal()
|
|
|
- })
|
|
|
- toggleModal()
|
|
|
- }
|
|
|
- const initData = async payload => {
|
|
|
- if (timer.current) clearTimeout(timer.current)
|
|
|
- setState({ ...state, disabled: true })
|
|
|
- const {
|
|
|
- code = -1,
|
|
|
- data: {
|
|
|
- reminderData = [],
|
|
|
- leaderboard = [],
|
|
|
- productLeaderBoard = {},
|
|
|
- clientChainRatio = [],
|
|
|
- customerChainRatio = {},
|
|
|
- businessChainRatio = {},
|
|
|
- serviceLogChainRatio = {},
|
|
|
- aggregation = {},
|
|
|
- businessChar = {}
|
|
|
- }
|
|
|
- } = await queryDashboard(payload)
|
|
|
- if (code === consts.RET_CODE.SUCCESS) {
|
|
|
- if (state.immediate) {
|
|
|
- setState({
|
|
|
- ...state,
|
|
|
- loading: false,
|
|
|
- immediate: false,
|
|
|
- reminderData,
|
|
|
- leaderboard,
|
|
|
- productLeaderBoard,
|
|
|
- clientChainRatio,
|
|
|
- customerChainRatio,
|
|
|
- businessChainRatio,
|
|
|
- serviceLogChainRatio,
|
|
|
- aggregation,
|
|
|
- disabled: false,
|
|
|
- businessChar
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- timer.current = setTimeout(() => {
|
|
|
- setState({
|
|
|
- ...state,
|
|
|
- loading: false,
|
|
|
- reminderData,
|
|
|
- leaderboard,
|
|
|
- productLeaderBoard,
|
|
|
- clientChainRatio,
|
|
|
- customerChainRatio,
|
|
|
- businessChainRatio,
|
|
|
- serviceLogChainRatio,
|
|
|
- aggregation,
|
|
|
- disabled: false,
|
|
|
- businessChar
|
|
|
- })
|
|
|
- }, 300)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- initData(state.params)
|
|
|
- }, [
|
|
|
- state.params.businessGroupId,
|
|
|
- state.params.dataPermission,
|
|
|
- state.params.cyclical,
|
|
|
- state.params.staffIds
|
|
|
- ])
|
|
|
- const columns = [
|
|
|
- {
|
|
|
- title: '超过周期未服务提醒',
|
|
|
- dataIndex: 'name'
|
|
|
- },
|
|
|
- {
|
|
|
- title: '7天',
|
|
|
- dataIndex: 'day7',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, '7day')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- title: '15天',
|
|
|
- dataIndex: 'day15',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, '15day')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- title: '30天',
|
|
|
- dataIndex: 'day30',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, '30day')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- title: '3个月',
|
|
|
- dataIndex: 'month3',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, '3month')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- title: '6个月',
|
|
|
- dataIndex: 'month6',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, '6month')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- title: '备忘',
|
|
|
- dataIndex: 'remind',
|
|
|
- render: (name, record) => (
|
|
|
- <span
|
|
|
- onClick={() => handleReminderList(record.type, 'reminder')}
|
|
|
- className="text-primary cursor-pointer hover:text-[#967bbd]">
|
|
|
- {name}
|
|
|
- </span>
|
|
|
- )
|
|
|
- }
|
|
|
- ]
|
|
|
-
|
|
|
- const handleChangeGroupId = e =>
|
|
|
- setState({ ...state, immediate: true, params: { ...state.params, businessGroupId: e } })
|
|
|
-
|
|
|
- // 处理权限Select下拉组件OnChange事件
|
|
|
- const handlePermChange = ({ staffIds = [], dataPermission }) => {
|
|
|
- if (staffIds?.length) {
|
|
|
- setState({
|
|
|
- ...state,
|
|
|
- immediate: true,
|
|
|
- params: { ...state.params, staffIds, dataPermission: 'custom' }
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- setState({
|
|
|
- ...state,
|
|
|
- immediate: true,
|
|
|
- params: { ...state.params, staffIds: null, dataPermission }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- const handleCyclicalChange = value => {
|
|
|
- setAuthCache(WORKBENCH_CYCLICAL_KEY, value)
|
|
|
- setState({ ...state, immediate: true, params: { ...state.params, cyclical: value } })
|
|
|
- }
|
|
|
- return (
|
|
|
- <div className={styles.dashboard}>
|
|
|
- <div className="shadow-card">
|
|
|
- <ProTable
|
|
|
- bordered
|
|
|
- loading={state.loading}
|
|
|
- rowKey={record => record.type + Date.now()}
|
|
|
- columns={columns}
|
|
|
- dataSource={state.reminderData}
|
|
|
- search={false}
|
|
|
- toolBarRender={false}
|
|
|
- tableRender={(_, dom) => (
|
|
|
- <Card bodyStyle={{ padding: state.loading ? '24px' : 0 }} loading={state.loading}>
|
|
|
- {dom}
|
|
|
- </Card>
|
|
|
- )}
|
|
|
- pagination={false}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div className="mt-4">
|
|
|
- <PermSelect
|
|
|
- loading={state.loading}
|
|
|
- disabled={state.disabled}
|
|
|
- onConfirm={handlePermChange}
|
|
|
- dataType="workbench"
|
|
|
- />
|
|
|
- <span className="ml-4">
|
|
|
- <Select
|
|
|
- disabled={state.disabled}
|
|
|
- loading={state.loading}
|
|
|
- options={cyclicalOp}
|
|
|
- dropdownMatchSelectWidth={false}
|
|
|
- defaultValue={defaultCyclicalValue}
|
|
|
- onChange={handleCyclicalChange}
|
|
|
- />
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div className="mt-4">
|
|
|
- <Row gutter={24}>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 6 }}
|
|
|
- xl={{ span: 6 }}>
|
|
|
- <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0 shadow-card">
|
|
|
- {state.loading ? (
|
|
|
- <Skeleton active avatar />
|
|
|
- ) : (
|
|
|
- <Row className="cursor-pointer" onClick={() => handleRatioCard(cardTypeMap.CLIENT)}>
|
|
|
- <Col span={6}>
|
|
|
- <div className="w-full max-w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
- <AddressBook size="26" fill="#fff" />
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={18} flex="wrap">
|
|
|
- <div className="flex items-center justify-between ">
|
|
|
- <div className="text-2xl">{state.clientChainRatio.count}</div>
|
|
|
- <span
|
|
|
- className={[
|
|
|
- 'text-xl',
|
|
|
- state.clientChainRatio.percentage.startsWith('-')
|
|
|
- ? 'text-green-500'
|
|
|
- : 'text-red-500'
|
|
|
- ].join(' ')}>
|
|
|
- {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>
|
|
|
- </Row>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 6 }}
|
|
|
- xl={{ span: 6 }}>
|
|
|
- <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0 shadow-card">
|
|
|
- {state.loading ? (
|
|
|
- <Skeleton active avatar />
|
|
|
- ) : (
|
|
|
- <Row
|
|
|
- className="cursor-pointer"
|
|
|
- onClick={() => handleRatioCard(cardTypeMap.COMPANY)}>
|
|
|
- <Col span={6}>
|
|
|
- <div className="w-full max-w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
- <City size="26" fill="#fff" />
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={18} flex="wrap">
|
|
|
- <div className="flex items-center justify-between ">
|
|
|
- <div className="text-2xl">{state.customerChainRatio.count}</div>
|
|
|
- <span
|
|
|
- className={[
|
|
|
- 'text-xl',
|
|
|
- state.customerChainRatio.percentage.startsWith('-')
|
|
|
- ? 'text-green-500'
|
|
|
- : 'text-red-500'
|
|
|
- ].join(' ')}>
|
|
|
- {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>
|
|
|
- </Row>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 6 }}
|
|
|
- xl={{ span: 6 }}>
|
|
|
- <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0 shadow-card">
|
|
|
- {state.loading ? (
|
|
|
- <Skeleton active avatar />
|
|
|
- ) : (
|
|
|
- <Row
|
|
|
- className="cursor-pointer"
|
|
|
- onClick={() => handleRatioCard(cardTypeMap.BUSINESS)}>
|
|
|
- <Col span={6}>
|
|
|
- <div className="w-full max-w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
- <Finance size="26" fill="#fff" />
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={18} flex="wrap">
|
|
|
- <div className="flex items-center justify-between ">
|
|
|
- <div className="text-2xl">{state.businessChainRatio.count}</div>
|
|
|
- <span
|
|
|
- className={[
|
|
|
- 'text-xl',
|
|
|
- state.businessChainRatio.percentage.startsWith('-')
|
|
|
- ? 'text-green-500'
|
|
|
- : 'text-red-500'
|
|
|
- ].join(' ')}>
|
|
|
- {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>
|
|
|
- </Row>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 6 }}
|
|
|
- xl={{ span: 6 }}>
|
|
|
- <div className="p-4 bg-white border rounded-2px border-hex-f0f0f0 shadow-card">
|
|
|
- {state.loading ? (
|
|
|
- <Skeleton active avatar />
|
|
|
- ) : (
|
|
|
- <Row
|
|
|
- onClick={() => handleRatioCard(cardTypeMap.SERVICE)}
|
|
|
- className="cursor-pointer">
|
|
|
- <Col span={6}>
|
|
|
- <div className="w-full max-w-48px h-48px border rounded-48px bg-[#0c7cd5] flex items-center justify-center">
|
|
|
- <Comment size="26" fill="#fff" />
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- <Col span={18} flex="wrap">
|
|
|
- <div className="flex items-center justify-between ">
|
|
|
- <div className="text-2xl">{state.serviceLogChainRatio.count}</div>
|
|
|
- <span
|
|
|
- className={[
|
|
|
- 'text-xl',
|
|
|
- state.serviceLogChainRatio.percentage.startsWith('-')
|
|
|
- ? 'text-green-500'
|
|
|
- : 'text-red-500'
|
|
|
- ].join(' ')}>
|
|
|
- {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>
|
|
|
- </Row>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
- </div>
|
|
|
- <div className="mt-4">
|
|
|
- <Row gutter={24}>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 16 }}
|
|
|
- xl={{ span: 16 }}>
|
|
|
- <BusinessChar
|
|
|
- loading={state.loading}
|
|
|
- data={state.businessChar}
|
|
|
- changeGroupId={handleChangeGroupId}
|
|
|
- />
|
|
|
- </Col>
|
|
|
-
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 8 }}
|
|
|
- xl={{ span: 8 }}>
|
|
|
- <Card
|
|
|
- headStyle={{ padding: '0 12px' }}
|
|
|
- title="数据汇总"
|
|
|
- className="shadow-card"
|
|
|
- bodyStyle={{ padding: state.loading ? '24px' : 0 }}
|
|
|
- loading={state.loading}>
|
|
|
- <ul>
|
|
|
- <li className="px-12px py-16px border-b-1">
|
|
|
- 新增客户<b className="px-1">{state.aggregation.clientCount}</b>个,服务
|
|
|
- <b className="px-1">{state.aggregation.clientServiceCount}</b> 条
|
|
|
- </li>
|
|
|
- <li className="px-12px py-16px border-b-1">
|
|
|
- 新增单位<b className="px-1">{state.aggregation.customerCount}</b>个,服务
|
|
|
- <b className="px-1">{state.aggregation.customerServiceCount}</b>条
|
|
|
- </li>
|
|
|
- <li className="px-12px py-16px">
|
|
|
- 新增商机<b className="px-1">{state.aggregation.businessCount}</b>个,金额
|
|
|
- <b className="px-1">{state.aggregation.businessSum}</b> 元,服务
|
|
|
- <b className="px-1">{state.aggregation.businessServiceCount}</b>
|
|
|
- 条,赢单<b className="px-1">{state.aggregation.businessWinCount}</b>个
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
- </div>
|
|
|
- <div className="mt-4">
|
|
|
- <Row gutter={24}>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 12 }}
|
|
|
- xl={{ span: 12 }}>
|
|
|
- <LeaderBoard dataList={state.leaderboard} loading={state.loading} />
|
|
|
- </Col>
|
|
|
- <Col
|
|
|
- className="gutter-row"
|
|
|
- sm={{ span: 24 }}
|
|
|
- md={{ span: 24 }}
|
|
|
- lg={{ span: 12 }}
|
|
|
- xl={{ span: 12 }}>
|
|
|
- <SoftLeaderboard
|
|
|
- productLeaderBoard={state.productLeaderBoard}
|
|
|
- loading={state.loading}
|
|
|
- />
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-export default Dashboard
|