开发: 添加客户列表
This commit is contained in:
@@ -69,7 +69,7 @@ export const formatTags = (data: any) => {
|
||||
if (data) {
|
||||
try {
|
||||
const tags = JSON.parse(data);
|
||||
if (Array.isArray(tags)) {
|
||||
if (Array.isArray(tags) && tags.length) {
|
||||
return (
|
||||
<>
|
||||
{tags.map((item) => {
|
||||
@@ -82,11 +82,9 @@ export const formatTags = (data: any) => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return <></>;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
return <></>;
|
||||
return <div>无标签</div>;
|
||||
};
|
||||
|
||||
type IGroupIcon = {
|
||||
|
@@ -22,7 +22,14 @@ export const Gender: React.FC<IProps> = (props) => {
|
||||
</svg>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
) : (
|
||||
<svg viewBox="0 0 1024 1024" width="16" height="16">
|
||||
<path
|
||||
d="M880.864 704.346c-56.813-25.397-141.118-90.818-265.945-113.298 31.924-34.36 56.084-88.097 80.937-151.754 14.403-36.893 11.935-68.343 11.935-113.121 0-33.073 6.193-86.156-1.97-115.329-27.533-98.59-97.1-125.79-178.61-125.79-81.577 0-151.229 27.328-178.71 126.087-8.097 29.083-1.888 82.044-1.888 115.037 0 44.875-2.422 76.432 12.019 113.383 25.044 63.984 48.854 117.644 80.655 151.86-123.826 22.89-207.498 87.822-263.95 113.138C58.523 756.945 57.95 814.296 57.95 814.296v97.42l940.724-0.112v-97.308s-0.547-57.548-117.809-109.95z"
|
||||
fill="#1890ff"
|
||||
></path>
|
||||
</svg>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -150,7 +150,7 @@
|
||||
pointer-events: none;
|
||||
|
||||
&.show {
|
||||
z-index: 1;
|
||||
z-index: 11;
|
||||
transform: translateY(0px);
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
@@ -109,8 +109,8 @@ const ChatLogs: React.FC = () => {
|
||||
setChatLogLoading(false);
|
||||
isAllChatRef.current = count < param.page_count * param.curr_page;
|
||||
if (res.err_code == 0) {
|
||||
if (Array.isArray(res.data) && res.data.length) {
|
||||
let arr: IChat[] = [];
|
||||
if (Array.isArray(res.data) && res.data.length) {
|
||||
const temp = res.data.reverse();
|
||||
const mark = { curr_page: param.curr_page, msg_time: temp[temp.length - 1].msg_time };
|
||||
if (param.curr_page == 1) {
|
||||
@@ -140,8 +140,8 @@ const ChatLogs: React.FC = () => {
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
setChatLogs(arr);
|
||||
}
|
||||
setChatLogs(arr);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -708,6 +708,18 @@ const ChatLogs: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div>备注名称:{selectCustFollow?.remark}</div>
|
||||
<div
|
||||
style={{
|
||||
paddingTop: 12,
|
||||
marginBottom: 12,
|
||||
paddingBottom: 12,
|
||||
borderBottom: '1px solid #ddd',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
标签
|
||||
</div>
|
||||
{formatTags(selectCustFollow?.tags)}
|
||||
</div>
|
||||
) : (
|
||||
|
@@ -0,0 +1,17 @@
|
||||
.modalAvatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
background-color: #69b1ff;
|
||||
border-radius: 6px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
@@ -1,85 +1,92 @@
|
||||
import { SearchBarPlugin, SearchBottonsCardPlugin } from '@/components/SearchBarPlugin';
|
||||
import { post } from '@/services/ajax';
|
||||
import { AddWay, CustType } from '@/services/config';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Button, Col, DatePicker, Drawer, Form, Input, Pagination, Row, Table } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
DatePicker,
|
||||
Drawer,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
Pagination,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import { stringify } from 'qs';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IStaffsItem } from '../ChatLogs/ChatLogsType';
|
||||
import { formatTags } from '../ChatLogs/ChatUtils';
|
||||
import { Gender } from '../ChatLogs/components/Gender';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
interface IDepartment {
|
||||
children: null | IDepartment[];
|
||||
department_leader: string;
|
||||
id: number;
|
||||
interface ICustItem {
|
||||
add_way: number;
|
||||
avatar: string;
|
||||
create_time: string;
|
||||
cust_id: string;
|
||||
description: string;
|
||||
gender: number;
|
||||
name: string;
|
||||
parent_id: number;
|
||||
sort: number;
|
||||
}
|
||||
|
||||
interface IStaffsData {
|
||||
count: number;
|
||||
data?: IStaffsItem[];
|
||||
oper_user_id: string;
|
||||
remark: string;
|
||||
remark_mobiles: string;
|
||||
state: number;
|
||||
tags: string;
|
||||
type: number;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
type Param = {
|
||||
curr_page: number;
|
||||
page_count: number;
|
||||
dep_id: number;
|
||||
type?: number | string;
|
||||
name?: string;
|
||||
position?: string;
|
||||
telephone?: string;
|
||||
mobile?: string;
|
||||
add_way?: string;
|
||||
create_timeL?: string;
|
||||
create_timeU?: string;
|
||||
};
|
||||
|
||||
const CustomList: React.FC = () => {
|
||||
const [param] = useState<Param>({
|
||||
curr_page: 1,
|
||||
page_count: 20,
|
||||
dep_id: 0,
|
||||
type: '',
|
||||
name: '',
|
||||
add_way: '',
|
||||
create_timeL: '',
|
||||
create_timeU: '',
|
||||
});
|
||||
|
||||
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 [custsList, setCustsList] = useState<ICustItem[]>([]);
|
||||
const [count, setCount] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [record, setRecord] = useState<IStaffsItem>();
|
||||
const [record, setRecord] = useState<ICustItem>();
|
||||
|
||||
const getStaffsList = () => {
|
||||
const getCustsList = () => {
|
||||
setLoading(true);
|
||||
post({ url: '/Staffs/List', data: stringify(param) }).then((res) => {
|
||||
post({ url: '/CustFollows/CustsList', 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();
|
||||
if (Array.isArray(res.data)) {
|
||||
setCustsList(res.data);
|
||||
}
|
||||
setCount(res.count || 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const page = (page: number) => {
|
||||
param.curr_page = page;
|
||||
getStaffsList();
|
||||
getCustsList();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDepartmentsList();
|
||||
getCustsList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -88,9 +95,10 @@ const CustomList: React.FC = () => {
|
||||
<SearchBarPlugin>
|
||||
<Form autoComplete="off">
|
||||
<Row gutter={{ xs: 0, sm: 16 }}>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<Col xs={24} sm={12} lg={8} xxl={6}>
|
||||
<Form.Item label="客户名称">
|
||||
<Input
|
||||
placeholder="请输入客户名称"
|
||||
autoComplete="off"
|
||||
onChange={(e) => {
|
||||
param.name = e.target.value.trim();
|
||||
@@ -100,54 +108,58 @@ const CustomList: React.FC = () => {
|
||||
/>
|
||||
</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();
|
||||
<Col xs={24} sm={12} lg={8} xxl={6}>
|
||||
<Form.Item label={<span>客户类型</span>}>
|
||||
<Select
|
||||
defaultValue={param.type}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(val) => {
|
||||
param.type = val;
|
||||
}}
|
||||
allowClear
|
||||
onPressEnter={() => page(1)}
|
||||
/>
|
||||
>
|
||||
<Select.Option value="">全部</Select.Option>
|
||||
{Object.keys(CustType).map((key) => {
|
||||
return (
|
||||
<Select.Option key={key} value={key}>
|
||||
{CustType[key]}
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</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();
|
||||
<Col xs={24} sm={12} lg={8} xxl={6}>
|
||||
<Form.Item label={<span>添加方式</span>}>
|
||||
<Select
|
||||
defaultValue={param.add_way}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(val) => {
|
||||
param.add_way = val;
|
||||
}}
|
||||
allowClear
|
||||
onPressEnter={() => page(1)}
|
||||
/>
|
||||
>
|
||||
<Select.Option value="">全部</Select.Option>
|
||||
{Object.keys(AddWay).map((key) => {
|
||||
return (
|
||||
<Select.Option key={key} value={key}>
|
||||
{AddWay[key]}
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<Form.Item label={<span style={{ textIndent: '1em' }}>手机号</span>}>
|
||||
<Col xs={24} sm={12} lg={8} xxl={6}>
|
||||
<Form.Item label={<span>添加日期</span>}>
|
||||
<DatePicker.RangePicker
|
||||
style={{ width: '100%' }}
|
||||
onChange={(dates, dateStrings) => {
|
||||
console.log(dates, dateStrings);
|
||||
// console.log(dateStrings);
|
||||
param.create_timeL = dateStrings[0];
|
||||
param.create_timeU = dateStrings[1];
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
@@ -167,58 +179,203 @@ const CustomList: React.FC = () => {
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
size="middle"
|
||||
dataSource={staffsData.data}
|
||||
dataSource={custsList}
|
||||
style={{ padding: 16, borderRadius: 6, background: '#fff', marginTop: 16 }}
|
||||
pagination={false}
|
||||
bordered={true}
|
||||
rowKey={'user_id'}
|
||||
rowKey={(record) => {
|
||||
return `${record.cust_id}_${record.user_id}`;
|
||||
}}
|
||||
loading={loading}
|
||||
>
|
||||
<Table.Column
|
||||
title="客户"
|
||||
width={200}
|
||||
dataIndex={'name'}
|
||||
render={(value, record: IStaffsItem) => {
|
||||
render={(value, record: ICustItem) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
setRecord(record);
|
||||
setOpen(true);
|
||||
}}
|
||||
style={{ color: '#1890ff', cursor: 'pointer' }}
|
||||
style={{ cursor: 'pointer', display: 'flex' }}
|
||||
>
|
||||
<span
|
||||
<div
|
||||
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',
|
||||
width: 56,
|
||||
height: 56,
|
||||
flexShrink: 0,
|
||||
borderRadius: 4,
|
||||
padding: '2px 4px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
负责人
|
||||
<Image width={56} height={56} src={record.avatar}></Image>
|
||||
</div>
|
||||
<div style={{ marginLeft: 8 }}>
|
||||
<div style={{ color: '#1890ff', wordBreak: 'break-all' }}>
|
||||
{value}[{record.remark}]
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Gender gender={record.gender}></Gender>
|
||||
<span style={{ marginLeft: 4, color: '#389e0d' }}>
|
||||
@{CustType[record.type]}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</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.Column
|
||||
title="客户标签"
|
||||
width={160}
|
||||
onCell={() => {
|
||||
return {
|
||||
style: {
|
||||
paddingBottom: 8,
|
||||
},
|
||||
};
|
||||
}}
|
||||
render={(value) => {
|
||||
try {
|
||||
let arr = JSON.parse(value);
|
||||
if (arr.length) {
|
||||
return (
|
||||
<>
|
||||
{arr.length >= 3 ? (
|
||||
<Popover
|
||||
content={() => {
|
||||
return (
|
||||
<>
|
||||
{arr.map((item: any) => {
|
||||
return (
|
||||
<Tag
|
||||
key={`${item?.group_name}:${item?.tag_name}`}
|
||||
color="green"
|
||||
title={`${item?.group_name}:${item?.tag_name}`}
|
||||
style={{ marginBottom: 4 }}
|
||||
>
|
||||
{item?.tag_name}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Tag
|
||||
color="green"
|
||||
title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`}
|
||||
style={{ marginBottom: 4 }}
|
||||
>
|
||||
{arr[0]?.tag_name}
|
||||
</Tag>
|
||||
{arr[1] ? (
|
||||
<Tag
|
||||
color="green"
|
||||
title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`}
|
||||
style={{ marginBottom: 4 }}
|
||||
>
|
||||
{arr[1]?.tag_name}
|
||||
</Tag>
|
||||
) : null}
|
||||
<Tag color="green" style={{ marginBottom: 4 }}>
|
||||
...
|
||||
</Tag>
|
||||
</Popover>
|
||||
) : (
|
||||
<>
|
||||
<Tag
|
||||
color="green"
|
||||
title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`}
|
||||
style={{ marginBottom: 4 }}
|
||||
>
|
||||
{arr[0]?.tag_name}
|
||||
</Tag>
|
||||
{arr[1] ? (
|
||||
<Tag
|
||||
color="green"
|
||||
title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`}
|
||||
style={{ marginBottom: 4 }}
|
||||
>
|
||||
{arr[1]?.tag_name}
|
||||
</Tag>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
return <div style={{ marginBottom: 4 }}>无标签</div>;
|
||||
}}
|
||||
dataIndex={'tags'}
|
||||
/>
|
||||
<Table.Column title="跟进员工" width={120} dataIndex={'user_id'} />
|
||||
{/* <Table.Column title="商机阶段" width={160} dataIndex={'position'} /> */}
|
||||
<Table.Column
|
||||
title="添加方式"
|
||||
width={140}
|
||||
dataIndex={'add_way'}
|
||||
render={(value) => {
|
||||
return <>{AddWay[value]}</>;
|
||||
}}
|
||||
/>
|
||||
<Table.Column title="添加时间" width={140} dataIndex={'create_time'} />
|
||||
{/* <Table.Column title="操作" width={160} dataIndex={'position'} /> */}
|
||||
</Table>
|
||||
<Drawer title="成员详情" open={open} onClose={() => setOpen(false)} width={800} />
|
||||
<Drawer
|
||||
title={`${record?.name} 客户详情`}
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
width={800}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
borderBottom: '1px solid #ddd',
|
||||
paddingBottom: 12,
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<div className={styles.modalAvatar}>
|
||||
<img src={record?.avatar} alt="" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ fontSize: 16 }}>
|
||||
<span style={{ marginRight: 8 }}>{record?.name}</span>
|
||||
<Gender gender={record?.gender} />
|
||||
</div>
|
||||
<span style={{ marginLeft: 4, color: '#389e0d' }}>
|
||||
@{record ? CustType[record?.type] : ''}
|
||||
</span>
|
||||
<div style={{ color: '#666' }}>{record?.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>备注名称:{record?.remark}</div>
|
||||
<div>添加方式:{record ? AddWay[record?.add_way] : ''}</div>
|
||||
<div>跟进员工:{record?.user_id}</div>
|
||||
<div>添加时间:{record?.create_time}</div>
|
||||
<div
|
||||
style={{
|
||||
paddingTop: 12,
|
||||
marginBottom: 12,
|
||||
paddingBottom: 12,
|
||||
borderBottom: '1px solid #ddd',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
标签
|
||||
</div>
|
||||
{formatTags(record?.tags)}
|
||||
</div>
|
||||
</Drawer>
|
||||
<Pagination
|
||||
style={{
|
||||
background: '#fff',
|
||||
@@ -231,7 +388,7 @@ const CustomList: React.FC = () => {
|
||||
}}
|
||||
current={param.curr_page}
|
||||
pageSize={param.page_count}
|
||||
total={staffsData.count}
|
||||
total={count}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
onShowSizeChange={(current, size) => {
|
||||
param.page_count = size;
|
||||
|
@@ -78,13 +78,15 @@ const DepartmentsList: React.FC = () => {
|
||||
post({ url: '/Departments/List' }).then((res) => {
|
||||
setLoadingL(false);
|
||||
if (res.err_code == 0) {
|
||||
if (Array.isArray(res.data) && res.data.length) {
|
||||
if (Array.isArray(res.data)) {
|
||||
setDepartmentsList(res.data);
|
||||
if (res.data.length) {
|
||||
param.dep_id = res.data[0].id;
|
||||
setDepartmentsID(param.dep_id);
|
||||
setDepartmentsList(res.data);
|
||||
getStaffsList();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -246,7 +248,7 @@ const DepartmentsList: React.FC = () => {
|
||||
<SearchBarPlugin>
|
||||
<Form autoComplete="off">
|
||||
<Row gutter={{ xs: 0, sm: 16 }}>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<Col xs={24} lg={12} xl={8}>
|
||||
<Form.Item label={<span style={{ textIndent: '1em' }}>姓名</span>}>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
@@ -258,7 +260,7 @@ const DepartmentsList: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<Col xs={24} lg={12} xl={8}>
|
||||
<Form.Item label={<span style={{ textIndent: '1em' }}>职务</span>}>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
@@ -270,7 +272,7 @@ const DepartmentsList: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<Col xs={24} lg={12} xl={8}>
|
||||
<Form.Item label="手机号">
|
||||
<Input
|
||||
autoComplete="off"
|
||||
@@ -282,20 +284,6 @@ const DepartmentsList: React.FC = () => {
|
||||
/>
|
||||
</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>
|
||||
|
@@ -1,13 +1,6 @@
|
||||
import Footer from '@/components/Footer';
|
||||
import { IAjaxReturn, post } from '@/services/ajax';
|
||||
import {
|
||||
AlipayCircleOutlined,
|
||||
LockOutlined,
|
||||
MobileOutlined,
|
||||
TaobaoCircleOutlined,
|
||||
UserOutlined,
|
||||
WeiboCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { LockOutlined, MobileOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
LoginForm,
|
||||
ProFormCaptcha,
|
||||
@@ -15,59 +8,13 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { useEmotionCss } from '@ant-design/use-emotion-css';
|
||||
import { FormattedMessage, Helmet, history, SelectLang, useModel } from '@umijs/max';
|
||||
import { FormattedMessage, Helmet, history, useModel } from '@umijs/max';
|
||||
import { Alert, App, Tabs } from 'antd';
|
||||
import { stringify as qsStringify } from 'qs';
|
||||
import React, { useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import Settings from '../../../../config/defaultSettings';
|
||||
|
||||
const ActionIcons = () => {
|
||||
const langClassName = useEmotionCss(({ token }) => {
|
||||
return {
|
||||
marginLeft: '8px',
|
||||
color: 'rgba(0, 0, 0, 0.2)',
|
||||
fontSize: '24px',
|
||||
verticalAlign: 'middle',
|
||||
cursor: 'pointer',
|
||||
transition: 'color 0.3s',
|
||||
'&:hover': {
|
||||
color: token.colorPrimaryActive,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
|
||||
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
|
||||
<WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Lang = () => {
|
||||
const langClassName = useEmotionCss(({ token }) => {
|
||||
return {
|
||||
width: 42,
|
||||
height: 42,
|
||||
lineHeight: '42px',
|
||||
position: 'fixed',
|
||||
right: 16,
|
||||
borderRadius: token.borderRadius,
|
||||
':hover': {
|
||||
backgroundColor: token.colorBgTextHover,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={langClassName} data-lang>
|
||||
{SelectLang && <SelectLang />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LoginMessage: React.FC<{
|
||||
content: string;
|
||||
}> = ({ content }) => {
|
||||
@@ -100,17 +47,6 @@ const Login: React.FC = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
const userInfo = await initialState?.fetchUserInfo?.();
|
||||
if (userInfo) {
|
||||
flushSync(() => {
|
||||
setInitialState((s) => ({
|
||||
...s,
|
||||
currentUser: userInfo,
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
const { notification } = App.useApp();
|
||||
|
||||
window.NotificationCF = notification;
|
||||
@@ -154,24 +90,6 @@ const Login: React.FC = () => {
|
||||
padding: '32px 0',
|
||||
}}
|
||||
>
|
||||
{/* <ChatTime msgtime={1680713999000}></ChatTime>
|
||||
<ChatTime msgtime={1680763041000}></ChatTime>
|
||||
<ChatTime msgtime={1680710399000}></ChatTime>
|
||||
<ChatTime msgtime={1680623999000}></ChatTime>
|
||||
<ChatTime msgtime={1680537599000}></ChatTime>
|
||||
<ChatTime msgtime={1680451199000}></ChatTime>
|
||||
<ChatTime msgtime={1680331041000}></ChatTime>
|
||||
<ChatTime msgtime={1680195599000}></ChatTime>
|
||||
<ChatTime msgtime={1680244641000}></ChatTime>
|
||||
<ChatTime msgtime={1680191999000}></ChatTime>
|
||||
<ChatTime msgtime={1680158241000}></ChatTime>
|
||||
<ChatRevoke></ChatRevoke>
|
||||
<ChatAgreeOrNot />
|
||||
<ChatVoice></ChatVoice>
|
||||
<ChatVideo></ChatVideo>
|
||||
<ChatCard></ChatCard>
|
||||
<ChatEmotion></ChatEmotion>
|
||||
<ChatBar></ChatBar> */}
|
||||
<LoginForm
|
||||
contentStyle={{
|
||||
minWidth: 280,
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { post } from '@/services/ajax';
|
||||
import { Area, Line } from '@ant-design/charts';
|
||||
import { BarChartOutlined, CommentOutlined, TeamOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
@@ -6,31 +7,84 @@ import { DataItemCard } from './components/DataItemCard';
|
||||
import { DataSumItemCard } from './components/DataSumItemCard';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const Workbench: React.FC = () => {
|
||||
const [data, setData] = useState([]);
|
||||
type ICustChartItem = {
|
||||
name: string;
|
||||
num: number;
|
||||
time: string;
|
||||
};
|
||||
|
||||
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);
|
||||
type IData = {
|
||||
overView: {
|
||||
custs_total: number;
|
||||
groups_members_total: number;
|
||||
groups_total: number;
|
||||
};
|
||||
custsInfo: {
|
||||
month_custs: number;
|
||||
today_custs: number;
|
||||
today_loss: number;
|
||||
today_net_growth: number;
|
||||
};
|
||||
groupsInfo: {
|
||||
today_diss_groups: number;
|
||||
today_diss_groups_members: number;
|
||||
today_groups: number;
|
||||
today_groups_members: number;
|
||||
};
|
||||
custsChart: ICustChartItem[];
|
||||
groupsChart: ICustChartItem[];
|
||||
};
|
||||
|
||||
const Workbench: React.FC = () => {
|
||||
const [data, setData] = useState<IData>({
|
||||
overView: {
|
||||
custs_total: 0,
|
||||
groups_members_total: 0,
|
||||
groups_total: 0,
|
||||
},
|
||||
custsInfo: {
|
||||
month_custs: 0,
|
||||
today_custs: 0,
|
||||
today_loss: 0,
|
||||
today_net_growth: 0,
|
||||
},
|
||||
groupsInfo: {
|
||||
today_diss_groups: 0,
|
||||
today_diss_groups_members: 0,
|
||||
today_groups: 0,
|
||||
today_groups_members: 0,
|
||||
},
|
||||
custsChart: [],
|
||||
groupsChart: [],
|
||||
});
|
||||
|
||||
const getWorkBenchInfo = () => {
|
||||
post({ url: '/WorkBench/Info' }).then((res) => {
|
||||
if (res.err_code == 0) {
|
||||
if (res.over_view) {
|
||||
data.overView = res.over_view;
|
||||
}
|
||||
if (res.groups_info) {
|
||||
data.groupsInfo = res.groups_info;
|
||||
}
|
||||
if (res.custs_info) {
|
||||
data.custsInfo = res.custs_info;
|
||||
}
|
||||
if (Array.isArray(res.custs_chart)) {
|
||||
data.custsChart = res.custs_chart;
|
||||
}
|
||||
if (Array.isArray(res.groups_chart)) {
|
||||
data.groupsChart = res.groups_chart;
|
||||
}
|
||||
setData({ ...data });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
asyncFetch();
|
||||
getWorkBenchInfo();
|
||||
}, []);
|
||||
|
||||
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' }}>
|
||||
@@ -42,67 +96,72 @@ const Workbench: React.FC = () => {
|
||||
padding: '0 24px',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 16 }}>数据总览</span>
|
||||
<span style={{ color: '#999' }}>更新时间:2022-12-12 23:30:30</span>
|
||||
<span style={{ fontSize: 18 }}>数据总览</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}
|
||||
count={data.overView.custs_total}
|
||||
/>
|
||||
<DataSumItemCard
|
||||
title="客群数量"
|
||||
content="当前员工权限范围内的全部客群数量"
|
||||
icon={<CommentOutlined className={styles.icon} />}
|
||||
count={111}
|
||||
count={data.overView.groups_total}
|
||||
/>
|
||||
<DataSumItemCard
|
||||
title="客群成员总数"
|
||||
content="当前员工权限范围内客群成员的总数(含员工)(含重复)"
|
||||
icon={<BarChartOutlined className={styles.icon} />}
|
||||
count={111}
|
||||
count={data.overView.groups_members_total}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.dataSum}>
|
||||
<div style={{ fontSize: 16 }}>客户数据</div>
|
||||
<div style={{ fontSize: 18 }}>客户数据</div>
|
||||
<div className={styles.dataCardBox}>
|
||||
<DataItemCard
|
||||
title="今日新增客户"
|
||||
content="当前员工权限范围内今日添加的客户数(含重复及流失)"
|
||||
count={11}
|
||||
count={data.custsInfo.today_custs}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
{/* <DataItemCard
|
||||
title="今日跟进客户"
|
||||
content="当前员工权限范围内今日处于跟进中状态的客户数(含重复)"
|
||||
count={11}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
/> */}
|
||||
{/* <span style={{ width: 16 }} /> */}
|
||||
<DataItemCard
|
||||
title="今日净增客户"
|
||||
content="当前员工权限范围内今日添加的客户数(不含重复及流失)"
|
||||
count={11}
|
||||
count={data.custsInfo.today_net_growth}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
title="今日流失客户"
|
||||
content="当前员工权限范围内的流失的全部客户数量"
|
||||
count={11}
|
||||
count={data.custsInfo.today_loss}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
title="本月新增客户"
|
||||
content="当前员工权限范围内本月添加的客户数(含重复及流失)"
|
||||
count={data.custsInfo.month_custs}
|
||||
/>
|
||||
{/* <DataItemCard
|
||||
title="昨日发送申请"
|
||||
content="当前员工数权限范围内主动向客户发起的申请数"
|
||||
count={11}
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
<Line
|
||||
data={data}
|
||||
xField="year"
|
||||
yField="gdp"
|
||||
data={data.custsChart}
|
||||
xField="time"
|
||||
yField="num"
|
||||
seriesField="name"
|
||||
// yAxis= {{
|
||||
// label: {
|
||||
@@ -123,37 +182,37 @@ const Workbench: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.dataSum}>
|
||||
<div style={{ fontSize: 16 }}>客群数据</div>
|
||||
<div style={{ fontSize: 18 }}>客群数据</div>
|
||||
<div className={styles.dataCardBox}>
|
||||
<DataItemCard
|
||||
title="今日新增客群"
|
||||
content="当前员工权限范围内今日创建的客群数"
|
||||
count={11}
|
||||
count={data.groupsInfo.today_groups}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
title="今日解散客群"
|
||||
content="当前员工权限范围内今日解散的客群数"
|
||||
count={11}
|
||||
count={data.groupsInfo.today_diss_groups}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
title="今日新增成员"
|
||||
content="当前员工权限范围内今日新增客群成员数(含员工)"
|
||||
count={11}
|
||||
count={data.groupsInfo.today_groups_members}
|
||||
/>
|
||||
<span style={{ width: 16 }} />
|
||||
<DataItemCard
|
||||
title="今日退出成员"
|
||||
content="当前员工权限范围内今日退出客群成员数(含员工)"
|
||||
count={11}
|
||||
count={data.groupsInfo.today_diss_groups_members}
|
||||
/>
|
||||
</div>
|
||||
<Area
|
||||
data={dataArea}
|
||||
xField="date"
|
||||
yField="value"
|
||||
seriesField="type"
|
||||
data={data.groupsChart}
|
||||
xField="time"
|
||||
yField="num"
|
||||
seriesField="name"
|
||||
smooth={true}
|
||||
legend={{ position: 'top' }}
|
||||
isStack={false}
|
||||
|
31
src/services/config.ts
Normal file
31
src/services/config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 添加客户的来源
|
||||
*/
|
||||
export const AddWay: any = {
|
||||
0: '未知来源',
|
||||
1: '扫描二维码',
|
||||
2: '搜索手机号',
|
||||
3: '名片分享',
|
||||
4: '群聊',
|
||||
5: '手机通讯录',
|
||||
6: '微信联系人',
|
||||
8: '安装第三方应用时自动添加的客服人员',
|
||||
9: '搜索邮箱',
|
||||
10: '视频号添加',
|
||||
11: '通过日程参与人添加',
|
||||
12: '通过会议参与人添加',
|
||||
13: '添加微信好友对应的企业微信',
|
||||
14: '通过智慧硬件专属客服添加',
|
||||
15: '通过上门服务客服添加',
|
||||
16: '通过获客链接添加',
|
||||
201: '内部成员共享',
|
||||
202: '管理员/负责人分配',
|
||||
};
|
||||
|
||||
/**
|
||||
* 外部联系人的类型
|
||||
*/
|
||||
export const CustType: any = {
|
||||
1: '微信',
|
||||
2: '企业微信',
|
||||
};
|
Reference in New Issue
Block a user