开发: 添加及修改路由

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', // component: './Welcome',
// }, // },
{ {
path: '/departments', path: '/scrm',
name: 'scrm', name: 'scrm',
icon: 'crown', icon: 'crown',
// access: 'canAdmin', // access: 'canAdmin',
routes: [ routes: [
{ {
path: '/departments', path: '/scrm',
redirect: '/departments/page/list', redirect: '/scrm/custom/workbench',
}, },
{ {
path: '/departments/page', path: '/scrm/custom',
name: '部门管理', name: '客户管理',
// hideInBreadcrumb: true,
// component: './Admin',
routes: [ routes: [
{ {
path: '/departments/page', path: '/scrm/custom',
redirect: '/departments/page/list', 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: '部门员工', name: '部门员工',
icon: 'table', icon: 'table',
path: '/departments/page/list', path: '/scrm/dep/list',
component: './DepartmentsList', component: './DepartmentsList',
}, },
],
},
{
path: '/scrm/chat',
name: '聊天记录',
routes: [
{
path: '/scrm/chat',
redirect: '/scrm/chat/list',
},
{ {
name: '成员聊天', name: '成员聊天',
icon: 'table', icon: 'table',
path: '/departments/page/list2', path: '/scrm/chat/list',
component: './ChatLogs',
},
{
name: '客户聊天',
icon: 'table',
path: '/departments/page/list3',
component: './ChatLogs', 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: '/', path: '/',
redirect: '/departments', redirect: '/scrm',
}, },
{ {
path: '*', path: '*',

View File

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

View File

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

View File

@@ -5,6 +5,8 @@
* @returns * @returns
*/ */
import React, { useEffect, useState } from 'react';
export const groupMembersCount = (data: any[], state: any) => { export const groupMembersCount = (data: any[], state: any) => {
let count = 0; let count = 0;
data.forEach((item) => { data.forEach((item) => {
@@ -86,3 +88,35 @@ export const formatTags = (data: any) => {
} }
return <></>; 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 { ChatTime } from './components/ChatTime';
import { Gender } from './components/Gender'; import { Gender } from './components/Gender';
import styles from './index.module.scss'; import styles from './index.module.scss';
interface ISearchWord {
'0': string;
'1': string;
'2': string;
}
const ChatLogs: React.FC = () => { const ChatLogs: React.FC = () => {
const [param] = useState({ const [param] = useState({
curr_page: 1, curr_page: 1,
@@ -80,7 +76,7 @@ const ChatLogs: React.FC = () => {
'3': '离职继承完成', '3': '离职继承完成',
}; };
const [searchWord, setSearchWord] = useState<ISearchWord>({ const [searchWord, setSearchWord] = useState<any>({
'0': '', '0': '',
'1': '', '1': '',
'2': '', '2': '',
@@ -100,72 +96,6 @@ const ChatLogs: React.FC = () => {
setFlolowsBox(false); 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 = () => { const getChatLogsList = () => {
chatLogLoadingRef.current = true; chatLogLoadingRef.current = true;
setChatLogLoading(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 = () => { const getGroupList = () => {
post({ post({
url: '/Groups/GroupsList', 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 { notification } = App.useApp();
const tabContent = () => { const tabContent = () => {
if (tabKey == '0') { if (tabKey == '0') {
@@ -549,7 +545,7 @@ const ChatLogs: React.FC = () => {
<div <div
key={item.user_id} key={item.user_id}
className={styles.chatB} className={styles.chatB}
onClick={(e) => { onClick={() => {
setSelectCustFollow(undefined); setSelectCustFollow(undefined);
selectCustFollowRef.current = undefined; selectCustFollowRef.current = undefined;
setSelectInnerStaff(undefined); setSelectInnerStaff(undefined);
@@ -637,7 +633,7 @@ const ChatLogs: React.FC = () => {
setSearchWord({ ...searchWord }); setSearchWord({ ...searchWord });
}} }}
allowClear allowClear
></Input> />
</Form> </Form>
<Tabs <Tabs
items={tabs} items={tabs}
@@ -647,7 +643,7 @@ const ChatLogs: React.FC = () => {
onChange={(val) => { onChange={(val) => {
setTabKey(val); setTabKey(val);
}} }}
></Tabs> />
<div className={styles.chatBBox}>{tabContent()}</div> <div className={styles.chatBBox}>{tabContent()}</div>
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
@@ -688,9 +684,7 @@ const ChatLogs: React.FC = () => {
footer={false} footer={false}
> >
{tabKeyRef.current == '0' ? ( {tabKeyRef.current == '0' ? (
<DepartmentMembersDetail <DepartmentMembersDetail record={selectInnerStaff as IStaffsItem} />
record={selectInnerStaff as IStaffsItem}
></DepartmentMembersDetail>
) : tabKeyRef.current == '1' ? ( ) : tabKeyRef.current == '1' ? (
<div> <div>
<div <div
@@ -707,7 +701,7 @@ const ChatLogs: React.FC = () => {
<div style={{ display: 'flex', flexDirection: 'column' }}> <div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ fontSize: 16 }}> <div style={{ fontSize: 16 }}>
<span style={{ marginRight: 8 }}>{selectCustFollow?.name}</span> <span style={{ marginRight: 8 }}>{selectCustFollow?.name}</span>
<Gender gender={selectCustFollow?.gender}></Gender> <Gender gender={selectCustFollow?.gender} />
</div> </div>
<div style={{ color: '#666' }}>{selectCustFollow?.description}</div> <div style={{ color: '#666' }}>{selectCustFollow?.description}</div>
</div> </div>
@@ -718,7 +712,8 @@ const ChatLogs: React.FC = () => {
) : ( ) : (
<div> <div>
<div style={{ marginBottom: 8, textIndent: '2em' }}> <div style={{ marginBottom: 8, textIndent: '2em' }}>
{groupMembersObjRef.current[selectGroupRef.current?.owner]?.name}
{groupMembersObjRef.current[selectGroupRef.current?.owner as string]?.name}
</div> </div>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
{adminList(selectGroupRef.current?.admin_list, groupMembersObjRef.current)} {adminList(selectGroupRef.current?.admin_list, groupMembersObjRef.current)}
@@ -937,26 +932,26 @@ const ChatLogs: React.FC = () => {
</div> </div>
) : null} ) : null}
{chatLogs.map((item, i) => { {chatLogs.map((item) => {
if (item.curr_page) { if (item.curr_page) {
return ( return (
<div <div
key={item.curr_page} key={item.curr_page}
className={`curr_page${param.curr_page}`} className={`curr_page${param.curr_page}`}
style={{ height: 0 }} style={{ height: 0 }}
></div> />
); );
} else { } else {
return ( return (
<div key={item.msg_id}> <div key={item.msg_id}>
{/* {item.show_time ? <ChatTime msgtime={item.msg_time}></ChatTime> : null} */} {/* {item.show_time ? <ChatTime msgtime={item.msg_time}></ChatTime> : null} */}
<ChatTime msgtime={item.msg_time}></ChatTime> <ChatTime msgtime={item.msg_time} />
{tabKey == '2' ? ( {tabKey == '2' ? (
<ChatBar <ChatBar
from={selectStaff} from={selectStaff}
to={groupMembersObjRef.current[item.msg_from]} to={groupMembersObjRef.current[item.msg_from]}
chat={item} chat={item}
></ChatBar> />
) : ( ) : (
<ChatBar <ChatBar
from={selectStaff} from={selectStaff}
@@ -966,7 +961,7 @@ const ChatLogs: React.FC = () => {
: selectCustFollowRef.current : selectCustFollowRef.current
} }
chat={item} chat={item}
></ChatBar> />
)} )}
</div> </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, page_count: 20,
dep_id: 0, dep_id: 0,
}); });
const { notification, modal } = App.useApp(); const { notification } = App.useApp();
const [departmentID, setDepartmentsID] = useState<number>(0); const [departmentID, setDepartmentsID] = useState<number>(0);
const [departmentsList, setDepartmentsList] = useState<IDepartment[]>([]); const [departmentsList, setDepartmentsList] = useState<IDepartment[]>([]);
const [staffsData, setStaffsData] = useState<IStaffsData>({ count: 0, data: [] }); const [staffsData, setStaffsData] = useState<IStaffsData>({ count: 0, data: [] });
@@ -60,6 +60,19 @@ const DepartmentsList: React.FC = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [record, setRecord] = useState<IStaffsItem>(); 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 = () => { const getDepartmentsList = () => {
setLoadingL(true); setLoadingL(true);
post({ url: '/Departments/List' }).then((res) => { 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) => { const page = (page: number) => {
param.curr_page = page; param.curr_page = page;
@@ -162,7 +163,7 @@ const DepartmentsList: React.FC = () => {
blockNode blockNode
selectedKeys={[departmentID]} selectedKeys={[departmentID]}
defaultExpandAll defaultExpandAll
treeData={departmentsList} treeData={departmentsList as any}
fieldNames={{ title: 'name', key: 'id' }} fieldNames={{ title: 'name', key: 'id' }}
onSelect={(selectedKeys) => { onSelect={(selectedKeys) => {
if (selectedKeys.length) { if (selectedKeys.length) {
@@ -212,7 +213,7 @@ const DepartmentsList: React.FC = () => {
</div> </div>
); );
}} }}
></Tree> />
) : null} ) : null}
</Spin> </Spin>
</div> </div>
@@ -246,7 +247,7 @@ const DepartmentsList: React.FC = () => {
<Form autoComplete="off"> <Form autoComplete="off">
<Row gutter={{ xs: 0, sm: 16 }}> <Row gutter={{ xs: 0, sm: 16 }}>
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Form.Item label="姓名"> <Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<Input <Input
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
@@ -254,11 +255,11 @@ const DepartmentsList: React.FC = () => {
}} }}
allowClear allowClear
onPressEnter={() => page(1)} onPressEnter={() => page(1)}
></Input> />
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Form.Item label="职务"> <Form.Item label={<span style={{ textIndent: '1em' }}></span>}>
<Input <Input
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
@@ -266,7 +267,7 @@ const DepartmentsList: React.FC = () => {
}} }}
allowClear allowClear
onPressEnter={() => page(1)} onPressEnter={() => page(1)}
></Input> />
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
@@ -278,7 +279,7 @@ const DepartmentsList: React.FC = () => {
}} }}
allowClear allowClear
onPressEnter={() => page(1)} onPressEnter={() => page(1)}
></Input> />
</Form.Item> </Form.Item>
</Col> </Col>
{/* <Col xs={24} sm={12} md={6}> {/* <Col xs={24} sm={12} md={6}>
@@ -374,20 +375,20 @@ const DepartmentsList: React.FC = () => {
</div> </div>
); );
}} }}
></Table.Column> />
<Table.Column title="职务" width={160} dataIndex={'position'}></Table.Column> <Table.Column title="职务" width={160} dataIndex={'position'} />
<Table.Column <Table.Column
title="部门" title="部门"
dataIndex={'dep_name'} dataIndex={'dep_name'}
render={(val) => { render={(val) => {
return <>{val.join('')}</>; return <>{val.join('')}</>;
}} }}
></Table.Column> />
<Table.Column title="手机号" width={160} dataIndex={'mobile'}></Table.Column> <Table.Column title="手机号" width={160} dataIndex={'mobile'} />
<Table.Column title="企业邮箱" dataIndex={'biz_mail'}></Table.Column> <Table.Column title="企业邮箱" dataIndex={'biz_mail'} />
</Table> </Table>
<Drawer title="成员详情" open={open} onClose={() => setOpen(false)} width={800}> <Drawer title="成员详情" open={open} onClose={() => setOpen(false)} width={800}>
<DepartmentMembersDetail record={record as IStaffsItem}></DepartmentMembersDetail> <DepartmentMembersDetail record={record as IStaffsItem} />
</Drawer> </Drawer>
<Pagination <Pagination
style={{ style={{
@@ -408,10 +409,10 @@ const DepartmentsList: React.FC = () => {
// setParam({ ...param }); // setParam({ ...param });
page(1); page(1);
}} }}
showTotal={(total, range) => { showTotal={(total) => {
return <span style={{ lineHeight: 1 }}>{total}</span>; return <span style={{ lineHeight: 1 }}>{total}</span>;
}} }}
onChange={(curr, pageSize) => { onChange={(curr) => {
page(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;