开发: 添加及修改路由

This commit is contained in:
zhengw
2023-04-17 17:47:31 +08:00
parent f4644b6ba2
commit bc8bb916ee
12 changed files with 711 additions and 145 deletions

View File

@@ -29,58 +29,69 @@ export default [
// component: './Welcome',
// },
{
path: '/departments',
path: '/scrm',
name: 'scrm',
icon: 'crown',
// access: 'canAdmin',
routes: [
{
path: '/departments',
redirect: '/departments/page/list',
path: '/scrm',
redirect: '/scrm/custom/workbench',
},
{
path: '/departments/page',
name: '部门管理',
// hideInBreadcrumb: true,
// component: './Admin',
path: '/scrm/custom',
name: '客户管理',
routes: [
{
path: '/departments/page',
redirect: '/departments/page/list',
path: '/scrm/custom',
redirect: '/scrm/custom/workbench',
},
{
name: '工作台',
icon: 'table',
path: '/scrm/custom/workbench',
component: './Workbench',
},
{
name: '客户列表',
icon: 'table',
path: '/scrm/custom/list',
component: './CustomList',
},
],
},
{
path: '/scrm/dep',
name: '部门管理',
routes: [
{
path: '/scrm/dep',
redirect: '/scrm/dep/list',
},
{
name: '部门员工',
icon: 'table',
path: '/departments/page/list',
path: '/scrm/dep/list',
component: './DepartmentsList',
},
],
},
{
path: '/scrm/chat',
name: '聊天记录',
routes: [
{
path: '/scrm/chat',
redirect: '/scrm/chat/list',
},
{
name: '成员聊天',
icon: 'table',
path: '/departments/page/list2',
component: './ChatLogs',
},
{
name: '客户聊天',
icon: 'table',
path: '/departments/page/list3',
path: '/scrm/chat/list',
component: './ChatLogs',
},
],
},
// {
// path: '/departments/sub-page2',
// name: '商品',
// // component: './TableList',
// routes: [
// {
// name: '列表',
// icon: 'table',
// path: '/departments/sub-page2/list',
// component: './TableList',
// },
// ],
// },
],
},
@@ -92,7 +103,7 @@ export default [
// },
{
path: '/',
redirect: '/departments',
redirect: '/scrm',
},
{
path: '*',

View File

@@ -45,6 +45,7 @@
"not ie <= 10"
],
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.3.57",
"@ant-design/use-emotion-css": "1.0.4",
@@ -57,6 +58,7 @@
"rc-menu": "^9.8.2",
"rc-util": "^5.27.2",
"react": "^18.2.0",
"react-countup": "^6.4.2",
"react-dev-inspector": "^1.8.4",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0"

View File

@@ -106,7 +106,7 @@ export interface IChat {
}
export interface IChatItem {
from?: IStaffsItem;
to?: ICustFollow;
to?: ICustFollow | IStaffsItem | IGroup;
group?: any;
chat?: IChat;
}

View File

@@ -5,6 +5,8 @@
* @returns
*/
import React, { useEffect, useState } from 'react';
export const groupMembersCount = (data: any[], state: any) => {
let count = 0;
data.forEach((item) => {
@@ -86,3 +88,35 @@ export const formatTags = (data: any) => {
}
return <></>;
};
type IGroupIcon = {
groupList: any[];
};
/**
* todo 群头像拼接
* @param props
* @returns
*/
export const GroupIcon: React.FC<IGroupIcon> = (props) => {
const [list, setList] = useState<any>([[], [], []]);
useEffect(() => {
let temp: any = [[], [], []];
const { groupList } = props;
for (let index = 0; index < 9; index++) {
const element = groupList[index];
if (index < 3) {
temp[0].push(element);
} else if (index < 6) {
temp[1].push(element);
} else {
temp[2].push(element);
}
}
console.log(temp);
}, [props.groupList]);
// <div className={styles.avatar}>{item.name ? item.name[0] : '群'}</div>;
return <div></div>;
};

View File

@@ -12,11 +12,7 @@ import { ChatBar } from './components/ChatBar';
import { ChatTime } from './components/ChatTime';
import { Gender } from './components/Gender';
import styles from './index.module.scss';
interface ISearchWord {
'0': string;
'1': string;
'2': string;
}
const ChatLogs: React.FC = () => {
const [param] = useState({
curr_page: 1,
@@ -80,7 +76,7 @@ const ChatLogs: React.FC = () => {
'3': '离职继承完成',
};
const [searchWord, setSearchWord] = useState<ISearchWord>({
const [searchWord, setSearchWord] = useState<any>({
'0': '',
'1': '',
'2': '',
@@ -100,72 +96,6 @@ const ChatLogs: React.FC = () => {
setFlolowsBox(false);
}
// 监听DOM变动
const callback = function (mutationsList: any) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
document.querySelector('.curr_page' + param.curr_page)?.scrollIntoView(true);
} else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
const observer = new MutationObserver(callback);
useEffect(() => {
document.addEventListener('click', show, false);
getStaffsList();
observer.observe(chatBoxRef.current, { childList: true });
const myScript = document.createElement('script');
myScript.src = '/public/scripts/amrnb.js';
myScript.async = false;
document.body.appendChild(myScript);
return () => {
document.removeEventListener('click', show, false);
observer.disconnect();
document.body.removeChild(myScript);
};
}, []);
// 获取员工
const getStaffsList = () => {
post({ url: '/Staffs/Data' }).then((res) => {
if (res.err_code == 0) {
if (Array.isArray(res.data) && res.data.length) {
// setSelectStaff(res.data[0]);
// selectStaffRef.current = res.data[0];
res.data.forEach((element: IStaffsItem) => {
if (element.user_id == 'yangxb') {
setSelectStaff(element);
selectStaffRef.current = element;
}
});
setStaffsList(res.data);
setInnerStaffsList(res.data);
getCustFollowsList();
getGroupList();
}
}
});
};
const page = (curr: number) => {
param.curr_page = curr;
param.msg_from = selectStaffRef.current?.user_id + '';
if (tabKey == '0') {
param.msg_to_list = selectInnerStaffRef.current?.user_id + '';
} else if (tabKey == '1') {
param.msg_to_list = selectCustFollowRef.current?.cust_id + '';
} else {
param.room_id = selectGroupRef.current?.group_id + '';
}
timeShowRef.current = false;
getChatLogsList();
};
const getChatLogsList = () => {
chatLogLoadingRef.current = true;
setChatLogLoading(true);
@@ -216,6 +146,20 @@ const ChatLogs: React.FC = () => {
});
};
const page = (curr: number) => {
param.curr_page = curr;
param.msg_from = selectStaffRef.current?.user_id + '';
if (tabKey == '0') {
param.msg_to_list = selectInnerStaffRef.current?.user_id + '';
} else if (tabKey == '1') {
param.msg_to_list = selectCustFollowRef.current?.cust_id + '';
} else {
param.room_id = selectGroupRef.current?.group_id + '';
}
timeShowRef.current = false;
getChatLogsList();
};
const getGroupList = () => {
post({
url: '/Groups/GroupsList',
@@ -271,6 +215,58 @@ const ChatLogs: React.FC = () => {
});
};
// 获取员工
const getStaffsList = () => {
post({ url: '/Staffs/Data' }).then((res) => {
if (res.err_code == 0) {
if (Array.isArray(res.data) && res.data.length) {
setSelectStaff(res.data[0]);
selectStaffRef.current = res.data[0];
// res.data.forEach((element: IStaffsItem) => {
// if (element.user_id == 'yangxb') {
// setSelectStaff(element);
// selectStaffRef.current = element;
// }
// });
setStaffsList(res.data);
setInnerStaffsList(res.data);
getCustFollowsList();
getGroupList();
}
}
});
};
// 监听DOM变动
const callback = function (mutationsList: any) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
document.querySelector('.curr_page' + param.curr_page)?.scrollIntoView(true);
} else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
const observer = new MutationObserver(callback);
useEffect(() => {
document.addEventListener('click', show, false);
getStaffsList();
observer.observe(chatBoxRef.current, { childList: true });
const myScript = document.createElement('script');
myScript.src = '/public/scripts/amrnb.js';
myScript.async = false;
document.body.appendChild(myScript);
return () => {
document.removeEventListener('click', show, false);
observer.disconnect();
document.body.removeChild(myScript);
};
}, []);
// const { notification } = App.useApp();
const tabContent = () => {
if (tabKey == '0') {
@@ -549,7 +545,7 @@ const ChatLogs: React.FC = () => {
<div
key={item.user_id}
className={styles.chatB}
onClick={(e) => {
onClick={() => {
setSelectCustFollow(undefined);
selectCustFollowRef.current = undefined;
setSelectInnerStaff(undefined);
@@ -637,7 +633,7 @@ const ChatLogs: React.FC = () => {
setSearchWord({ ...searchWord });
}}
allowClear
></Input>
/>
</Form>
<Tabs
items={tabs}
@@ -647,7 +643,7 @@ const ChatLogs: React.FC = () => {
onChange={(val) => {
setTabKey(val);
}}
></Tabs>
/>
<div className={styles.chatBBox}>{tabContent()}</div>
</div>
<div style={{ flex: 1 }}>
@@ -688,9 +684,7 @@ const ChatLogs: React.FC = () => {
footer={false}
>
{tabKeyRef.current == '0' ? (
<DepartmentMembersDetail
record={selectInnerStaff as IStaffsItem}
></DepartmentMembersDetail>
<DepartmentMembersDetail record={selectInnerStaff as IStaffsItem} />
) : tabKeyRef.current == '1' ? (
<div>
<div
@@ -707,7 +701,7 @@ const ChatLogs: React.FC = () => {
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ fontSize: 16 }}>
<span style={{ marginRight: 8 }}>{selectCustFollow?.name}</span>
<Gender gender={selectCustFollow?.gender}></Gender>
<Gender gender={selectCustFollow?.gender} />
</div>
<div style={{ color: '#666' }}>{selectCustFollow?.description}</div>
</div>
@@ -718,7 +712,8 @@ const ChatLogs: React.FC = () => {
) : (
<div>
<div style={{ marginBottom: 8, textIndent: '2em' }}>
{groupMembersObjRef.current[selectGroupRef.current?.owner]?.name}
{groupMembersObjRef.current[selectGroupRef.current?.owner as string]?.name}
</div>
<div style={{ marginBottom: 8 }}>
{adminList(selectGroupRef.current?.admin_list, groupMembersObjRef.current)}
@@ -937,26 +932,26 @@ const ChatLogs: React.FC = () => {
</div>
) : null}
{chatLogs.map((item, i) => {
{chatLogs.map((item) => {
if (item.curr_page) {
return (
<div
key={item.curr_page}
className={`curr_page${param.curr_page}`}
style={{ height: 0 }}
></div>
/>
);
} else {
return (
<div key={item.msg_id}>
{/* {item.show_time ? <ChatTime msgtime={item.msg_time}></ChatTime> : null} */}
<ChatTime msgtime={item.msg_time}></ChatTime>
<ChatTime msgtime={item.msg_time} />
{tabKey == '2' ? (
<ChatBar
from={selectStaff}
to={groupMembersObjRef.current[item.msg_from]}
chat={item}
></ChatBar>
/>
) : (
<ChatBar
from={selectStaff}
@@ -966,7 +961,7 @@ const ChatLogs: React.FC = () => {
: selectCustFollowRef.current
}
chat={item}
></ChatBar>
/>
)}
</div>
);

View File

View File

@@ -0,0 +1,252 @@
import { SearchBarPlugin, SearchBottonsCardPlugin } from '@/components/SearchBarPlugin';
import { post } from '@/services/ajax';
import { PageContainer } from '@ant-design/pro-components';
import { Button, Col, DatePicker, Drawer, Form, Input, Pagination, Row, Table } from 'antd';
import { stringify } from 'qs';
import React, { useEffect, useState } from 'react';
import { IStaffsItem } from '../ChatLogs/ChatLogsType';
interface IDepartment {
children: null | IDepartment[];
department_leader: string;
id: number;
name: string;
parent_id: number;
sort: number;
}
interface IStaffsData {
count: number;
data?: IStaffsItem[];
}
type Param = {
curr_page: number;
page_count: number;
dep_id: number;
name?: string;
position?: string;
telephone?: string;
mobile?: string;
};
const CustomList: React.FC = () => {
const [param] = useState<Param>({
curr_page: 1,
page_count: 20,
dep_id: 0,
});
const [departmentID, setDepartmentsID] = useState<number>(0);
const [departmentsList, setDepartmentsList] = useState<IDepartment[]>([]);
const [staffsData, setStaffsData] = useState<IStaffsData>({ count: 0, data: [] });
const [loadingL, setLoadingL] = useState(false);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [record, setRecord] = useState<IStaffsItem>();
const getStaffsList = () => {
setLoading(true);
post({ url: '/Staffs/List', data: stringify(param) }).then((res) => {
setLoading(false);
if (res.err_code == 0) {
if (!Array.isArray(res.data)) {
res.data = [];
}
setStaffsData(res as IStaffsData);
}
});
};
const getDepartmentsList = () => {
setLoadingL(true);
post({ url: '/Departments/List' }).then((res) => {
setLoadingL(false);
if (res.err_code == 0) {
if (Array.isArray(res.data) && res.data.length) {
param.dep_id = res.data[0].id;
setDepartmentsID(param.dep_id);
setDepartmentsList(res.data);
getStaffsList();
}
}
});
};
const page = (page: number) => {
param.curr_page = page;
getStaffsList();
};
useEffect(() => {
getDepartmentsList();
}, []);
return (
<PageContainer>
<div style={{ flexGrow: 1, minWidth: 0 }}>
<SearchBarPlugin>
<Form autoComplete="off">
<Row gutter={{ xs: 0, sm: 16 }}>
<Col xs={24} sm={12} md={8}>
<Form.Item label="客户名称">
<Input
autoComplete="off"
onChange={(e) => {
param.name = e.target.value.trim();
}}
allowClear
onPressEnter={() => page(1)}
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
<Form.Item label={<span style={{ textIndent: '2em' }}></span>}>
<Input
autoComplete="off"
onChange={(e) => {
param.position = e.target.value.trim();
}}
allowClear
onPressEnter={() => page(1)}
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
<Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<Input
autoComplete="off"
onChange={(e) => {
param.mobile = e.target.value.trim();
}}
allowClear
onPressEnter={() => page(1)}
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
<Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<DatePicker.RangePicker
style={{ width: '100%' }}
onChange={(dates, dateStrings) => {
console.log(dates, dateStrings);
}}
/>
</Form.Item>
</Col>
{/* <Col xs={24} sm={12} md={6}>
<Form.Item label="用户名">
<Select
defaultValue="lucy"
style={{ width: '100%' }}
onChange={() => {}}
options={[
{ value: 'jack', label: 'Jack' },
{ value: 'lucy', label: 'Lucy' },
{ value: 'Yiminghe', label: 'yiminghe' },
]}
/>
</Form.Item>
</Col> */}
</Row>
</Form>
</SearchBarPlugin>
<SearchBottonsCardPlugin>
<Row justify={'center'}>
<Button
type="primary"
onClick={() => {
page(1);
}}
style={{ marginRight: 12 }}
>
</Button>
</Row>
</SearchBottonsCardPlugin>
<Table
tableLayout="fixed"
size="middle"
dataSource={staffsData.data}
style={{ padding: 16, borderRadius: 6, background: '#fff', marginTop: 16 }}
pagination={false}
bordered={true}
rowKey={'user_id'}
loading={loading}
>
<Table.Column
title="客户"
dataIndex={'name'}
render={(value, record: IStaffsItem) => {
return (
<div
onClick={() => {
setRecord(record);
setOpen(true);
}}
style={{ color: '#1890ff', cursor: 'pointer' }}
>
<span
style={{
display: 'inline-block',
marginRight: record.isleader == 1 ? 4 : 0,
}}
>
{value}
</span>
{record?.isleader == 1 ? (
<span
style={{
color: '#999',
fontSize: 12,
border: '1px solid #ddd',
borderRadius: 4,
padding: '2px 4px',
}}
>
</span>
) : null}
</div>
);
}}
/>
<Table.Column title="客户标签" width={160} dataIndex={'position'} />
<Table.Column title="跟进员工" width={160} dataIndex={'position'} />
<Table.Column title="商机阶段" width={160} dataIndex={'position'} />
<Table.Column title="添加方式" width={160} dataIndex={'position'} />
<Table.Column title="添加时间" width={160} dataIndex={'position'} />
<Table.Column title="操作" width={160} dataIndex={'position'} />
</Table>
<Drawer title="成员详情" open={open} onClose={() => setOpen(false)} width={800} />
<Pagination
style={{
background: '#fff',
borderRadius: 6,
marginTop: 16,
padding: 16,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
current={param.curr_page}
pageSize={param.page_count}
total={staffsData.count}
pageSizeOptions={[10, 20, 50, 100]}
onShowSizeChange={(current, size) => {
param.page_count = size;
page(1);
}}
showTotal={(total) => {
return <span style={{ lineHeight: 1 }}>{total}</span>;
}}
onChange={(curr) => {
page(curr);
}}
/>
</div>
</PageContainer>
);
};
export default CustomList;

View File

@@ -51,7 +51,7 @@ const DepartmentsList: React.FC = () => {
page_count: 20,
dep_id: 0,
});
const { notification, modal } = App.useApp();
const { notification } = App.useApp();
const [departmentID, setDepartmentsID] = useState<number>(0);
const [departmentsList, setDepartmentsList] = useState<IDepartment[]>([]);
const [staffsData, setStaffsData] = useState<IStaffsData>({ count: 0, data: [] });
@@ -60,6 +60,19 @@ const DepartmentsList: React.FC = () => {
const [open, setOpen] = useState(false);
const [record, setRecord] = useState<IStaffsItem>();
const getStaffsList = () => {
setLoading(true);
post({ url: '/Staffs/List', data: stringify(param) }).then((res) => {
setLoading(false);
if (res.err_code == 0) {
if (!Array.isArray(res.data)) {
res.data = [];
}
setStaffsData(res as IStaffsData);
}
});
};
const getDepartmentsList = () => {
setLoadingL(true);
post({ url: '/Departments/List' }).then((res) => {
@@ -74,18 +87,6 @@ const DepartmentsList: React.FC = () => {
}
});
};
const getStaffsList = () => {
setLoading(true);
post({ url: '/Staffs/List', data: stringify(param) }).then((res) => {
setLoading(false);
if (res.err_code == 0) {
if (!Array.isArray(res.data)) {
res.data = [];
}
setStaffsData(res as IStaffsData);
}
});
};
const page = (page: number) => {
param.curr_page = page;
@@ -162,7 +163,7 @@ const DepartmentsList: React.FC = () => {
blockNode
selectedKeys={[departmentID]}
defaultExpandAll
treeData={departmentsList}
treeData={departmentsList as any}
fieldNames={{ title: 'name', key: 'id' }}
onSelect={(selectedKeys) => {
if (selectedKeys.length) {
@@ -212,7 +213,7 @@ const DepartmentsList: React.FC = () => {
</div>
);
}}
></Tree>
/>
) : null}
</Spin>
</div>
@@ -246,7 +247,7 @@ const DepartmentsList: React.FC = () => {
<Form autoComplete="off">
<Row gutter={{ xs: 0, sm: 16 }}>
<Col xs={24} sm={12} md={8}>
<Form.Item label="姓名">
<Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<Input
autoComplete="off"
onChange={(e) => {
@@ -254,11 +255,11 @@ const DepartmentsList: React.FC = () => {
}}
allowClear
onPressEnter={() => page(1)}
></Input>
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
<Form.Item label="职务">
<Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<Input
autoComplete="off"
onChange={(e) => {
@@ -266,7 +267,7 @@ const DepartmentsList: React.FC = () => {
}}
allowClear
onPressEnter={() => page(1)}
></Input>
/>
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
@@ -278,7 +279,7 @@ const DepartmentsList: React.FC = () => {
}}
allowClear
onPressEnter={() => page(1)}
></Input>
/>
</Form.Item>
</Col>
{/* <Col xs={24} sm={12} md={6}>
@@ -374,20 +375,20 @@ const DepartmentsList: React.FC = () => {
</div>
);
}}
></Table.Column>
<Table.Column title="职务" width={160} dataIndex={'position'}></Table.Column>
/>
<Table.Column title="职务" width={160} dataIndex={'position'} />
<Table.Column
title="部门"
dataIndex={'dep_name'}
render={(val) => {
return <>{val.join('')}</>;
}}
></Table.Column>
<Table.Column title="手机号" width={160} dataIndex={'mobile'}></Table.Column>
<Table.Column title="企业邮箱" dataIndex={'biz_mail'}></Table.Column>
/>
<Table.Column title="手机号" width={160} dataIndex={'mobile'} />
<Table.Column title="企业邮箱" dataIndex={'biz_mail'} />
</Table>
<Drawer title="成员详情" open={open} onClose={() => setOpen(false)} width={800}>
<DepartmentMembersDetail record={record as IStaffsItem}></DepartmentMembersDetail>
<DepartmentMembersDetail record={record as IStaffsItem} />
</Drawer>
<Pagination
style={{
@@ -408,10 +409,10 @@ const DepartmentsList: React.FC = () => {
// setParam({ ...param });
page(1);
}}
showTotal={(total, range) => {
showTotal={(total) => {
return <span style={{ lineHeight: 1 }}>{total}</span>;
}}
onChange={(curr, pageSize) => {
onChange={(curr) => {
page(curr);
}}
/>

View File

@@ -0,0 +1,27 @@
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Popover } from 'antd';
import React from 'react';
import CountUp from 'react-countup';
import styles from '../index.module.scss';
type IProps = {
title: string;
content: string;
count: number;
};
export const DataItemCard: React.FC<IProps> = (props) => {
return (
<div className={styles.dataCard}>
<div>
<span style={{ marginRight: 8 }}>{props.title}</span>
<Popover placement="topLeft" content={props.content}>
<ExclamationCircleOutlined style={{ cursor: 'pointer', color: '#999' }} />
</Popover>
</div>
<div className={styles.dataCount}>
<CountUp end={props.count} duration={2}></CountUp>
</div>
</div>
);
};

View File

@@ -0,0 +1,30 @@
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Popover } from 'antd';
import React from 'react';
import CountUp from 'react-countup';
import styles from '../index.module.scss';
type IProps = {
title: string;
content: string;
count: number;
icon: React.ReactNode;
};
export const DataSumItemCard: React.FC<IProps> = (props) => {
return (
<div className={styles.dataItem}>
<div className={styles.dataIconBox}>{props.icon}</div>
<div>
<div>
<span style={{ marginRight: 8 }}>{props.title}</span>
<Popover placement="topLeft" content={props.content}>
<ExclamationCircleOutlined style={{ cursor: 'pointer', color: '#999' }} />
</Popover>
</div>
<div className={styles.dataCount}>
<CountUp end={props.count} duration={2} />
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,48 @@
.dataSum {
margin-bottom: 24px;
padding: 24px;
color: #000;
background-color: #fff;
border-radius: 8px;
}
.dataItem {
display: inline-flex;
flex: 1;
margin-bottom: 12px;
border-right: 1px solid #ddd;
padding-left: 24px;
}
.dataIconBox {
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
margin-right: 16px;
background-color: #edf2f9;
border-radius: 12px;
.icon {
color: #1890ff;
font-size: 32px;
}
}
.dataCount {
font-size: 24px;
}
.dataCardBox {
display: flex;
margin-top: 16px;
margin-bottom: 16px;
}
.dataCard {
padding: 16px;
border: 1px solid #ddd;
border-radius: 12px;
flex: 1;
}

View File

@@ -0,0 +1,166 @@
import { Area, Line } from '@ant-design/charts';
import { BarChartOutlined, CommentOutlined, TeamOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import React, { useEffect, useState } from 'react';
import { DataItemCard } from './components/DataItemCard';
import { DataSumItemCard } from './components/DataSumItemCard';
import styles from './index.module.scss';
const Workbench: React.FC = () => {
const [data, setData] = useState([]);
const asyncFetch = () => {
fetch('https://gw.alipayobjects.com/os/bmw-prod/e00d52f4-2fa6-47ee-a0d7-105dd95bde20.json')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => {
console.log('fetch data failed', error);
});
};
useEffect(() => {
asyncFetch();
}, []);
const [dataArea, setDataArea] = useState([
{ type: '订单数', date: '2021-02-01', value: 400 },
{ type: '订单数', date: '2021-02-05', value: 900 },
{ type: '订单数', date: '2021-02-08', value: 300 },
{ type: '收衣数', date: '2021-02-01', value: 700 },
{ type: '收衣数', date: '2021-02-05', value: 500 },
{ type: '收衣数', date: '2021-02-08', value: 1000 },
]);
return (
<PageContainer>
<div className={styles.dataSum} style={{ padding: '24px 0' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginBottom: 24,
padding: '0 24px',
}}
>
<span style={{ fontSize: 16 }}></span>
<span style={{ color: '#999' }}>2022-12-12 23:30:30</span>
</div>
<div style={{ display: 'flex' }}>
<DataSumItemCard
title="客户数量"
content="当前员工权限范围内的全部客户数量(含重复)"
icon={<TeamOutlined className={styles.icon} />}
count={111}
/>
<DataSumItemCard
title="客群数量"
content="当前员工权限范围内的全部客群数量"
icon={<CommentOutlined className={styles.icon} />}
count={111}
/>
<DataSumItemCard
title="客群成员总数"
content="当前员工权限范围内客群成员的总数(含员工)(含重复)"
icon={<BarChartOutlined className={styles.icon} />}
count={111}
/>
</div>
</div>
<div className={styles.dataSum}>
<div style={{ fontSize: 16 }}></div>
<div className={styles.dataCardBox}>
<DataItemCard
title="今日新增客户"
content="当前员工权限范围内今日添加的客户数(含重复及流失)"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日跟进客户"
content="当前员工权限范围内今日处于跟进中状态的客户数(含重复)"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日净增客户"
content="当前员工权限范围内今日添加的客户数(不含重复及流失)"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日流失客户"
content="当前员工权限范围内的流失的全部客户数量"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="昨日发送申请"
content="当前员工数权限范围内主动向客户发起的申请数"
count={11}
/>
</div>
<Line
data={data}
xField="year"
yField="gdp"
seriesField="name"
// yAxis= {{
// label: {
// formatter: (v: any) => `${(v / 10e8).toFixed(1)} B`,
// },
// },}
legend={{
position: 'top',
}}
smooth={true}
// @TODO 后续会换一种动画方式
animation={{
appear: {
animation: 'path-in',
duration: 3000,
},
}}
/>
</div>
<div className={styles.dataSum}>
<div style={{ fontSize: 16 }}></div>
<div className={styles.dataCardBox}>
<DataItemCard
title="今日新增客群"
content="当前员工权限范围内今日创建的客群数"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日解散客群"
content="当前员工权限范围内今日解散的客群数"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日新增成员"
content="当前员工权限范围内今日新增客群成员数(含员工)"
count={11}
/>
<span style={{ width: 16 }} />
<DataItemCard
title="今日退出成员"
content="当前员工权限范围内今日退出客群成员数(含员工)"
count={11}
/>
</div>
<Area
data={dataArea}
xField="date"
yField="value"
seriesField="type"
smooth={true}
legend={{ position: 'top' }}
isStack={false}
/>
</div>
</PageContainer>
);
};
export default Workbench;